Java基础(九)

一、总述

多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务称为一个线程,它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序。

多进程与多线程的区别:每个进程拥有自己的一整套变量,而线程则共享数据。

二、中断线程

interrupt方法可以用来请求终止线程。当对一个线程调用interrupt方法时,线程的中断状态将被置位。这是每一个线程都具有的boolean标志。

三、线程状态

线程可以有如下六种状态:(要确定一个线程的当前状态,可调用getState方法)

1、New(新创建):当一个线程处于新创建状态时,程序还没有开始运行线程中的代码。

2、Runnable(可运行):在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行。

3、Blocked(被阻塞):当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,则该线程将变成非阻塞状态。

4、Waiting(等待):当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。

5、Timed Waiteing(计时等待):有几个方法有一个超时参数。调用它们导致线程进入计时等待状态。这一状态将一直保持到超时期满或者接收到适当的通知。

6、Terminated(被终止):线程被终止的原因有两个:A、因为run方法正常退出而自然死亡;B、因为一个没有捕获的异常终止了run方法而意外死亡。

四、线程属性

1、线程优先级

在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。

可以用setPriority方法提高或降低任何一个线程的优先级,可以将优先级设置为MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值,其中NORM_PRIOROTY被定义为5.

每当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。

线程优先级是高度依赖于系统的。当虚拟机依赖于宿主机平台的线程实现机制时,Java线程的优先级被映射到宿主机平台的优先级上,Windows有7个优先级别。在Oracle为Linux提供的Java虚拟机中,线程的优先级被忽略——所有线程具有相同的优先级。

2、守护线程

可以通过调用

t.setDaemon(true);

将线程转换为守护线程。

守护线程的唯一用途是为其他线程提供服务。

当只剩下守护线程时,虚拟机就退出了,程序也将停止运行。

守护线程应该永远不去访问固有资源,如文件,数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

3、线程组

线程组是一个可以统一管理的线程集合。

五、同步

如果两个线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,根据各线程访问数据的次序可能会产生讹误的对象,这样一个情况通常称为竞争条件。

Java语言提供了一个synchronized关键字来防止代码受并发访问的干扰。synchronized关键字自动提供了一个锁以及相关的“条件”,对于大多数需要显示锁的情况,这是很便利的。

锁是可重入的,因为线程可以重复的获得已经持有的锁。锁保持一个持有计数来跟踪对lock方法的嵌套调用。

内部锁和条件存在一些局限:

1、不能中断一个正在试图获得锁的线程。

2、试图获得锁时不能设定超时。

3、每个锁仅有单一的条件,可能是不够的。

在代码中使用Lock/Condition对象还是同步方法?

1、最好既不使用Lock/Condition,也不使用synchronized关键字。在很多情况下你可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁。

2、如果synchronized关键字适合你的程序,那么尽量是用它,这样可减少编写的代码数量,减少出错的几率。

3、如果特别需要Lock/Condition结构提供的独有特性时,才使用Lock/Condition。

每一个Java对象有一个锁。线程可以通过调用同步方法获得锁。还有另一种机制可以获得锁,通过进入一个同步阻塞。

使用一个对象的锁来实现额外的原子操作,这种操作称为客户端锁定。

用Java的术语来讲,监视器具有如下特性:

1、监视器是只包含私有域的类。

2、每个监视器类的对象有一个相关的锁。

3、使用该锁对所有的方法进行加锁。换句话说,如果客户端调用obj.method(),那么obj对象的锁是在方法调用开始时自动获得,并且当方法返回时自动释放该锁。因为所有的域是私有的,这样的安排可以确保一个线程在对对象操作时,没有其他线程能访问该域。

4、该锁可以有任意多个相关条件。

Java中的每一个对象有一个内部的锁和内部的条件。如果一个方法用synchronized关键字声明,那么,它表现的就像是一个监视器方法,通过调用wait/notifyAll/notify来访问条件变量。

在下述的三个方面Java对象不同于监视器,从而使得线程的安全性下降:

1、域不要求必须是private。

2、方法不要求必须是synchronized。

3、内部锁对客户是可用的。

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。

还有一种情况可以安全的访问一个共享域,即这个域声明为final时。

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。

每一个线程因为某个条件需要等待而导致所有线程都被阻塞,这样的状态称为死锁。

线程在调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。

应该更加谨慎的申请锁。tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,而且线程可以立即离开去做其他的事。

使用读/写锁的必要步骤:

1、构造一个ReentrantReadWriteLock对象。

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2、抽取读锁和写锁。

private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

3、对所有的获取方法加读锁。

public double getTotalBalance()
{
    readLock.lock();
    try{ ... }
    finally{ readLock.unlock(); }  
}

4、对所有的修改方法加写锁。

public void transfer()
{
    writeLock.lock();
    try{ ... }
    finally{ writeLock.unlock(); }
}

六、阻塞队列

对于许多线程问题,可以通过使用一个或多个队列以优雅且安全的方式将其形式化。使用队列,可以安全的从一个线程向另一个线程传递数据。

当试图向队列添加元素而队列已满,或是想从队列中移出元素而队列为空的时候,阻塞队列导致线程阻塞。

java.util.concurrent包提供了阻塞队列的几种变种:

1、LinkedBlockingQueue在默认情况下,容量没有上边界,但是,可以选择指定最大容量。同时,它还是一个双端的版本。

2、ArrayBlockingQueue在构造时需要指定容量,并且有一个可选的参数来指定是否需要公平性。若设置了公平参数,则那么等待了最长时间的线程会优先得到处理。

3、PriorityBlockingQueue是一个带优先级的队列,而不是先进先出队列。元素按照它们的优先级顺序被移出。该队列是没有容量上限,但是,如果队列是空的,取元素的操作会阻塞。

4、DelayQueue包含实现Delayed接口的对象:

interface Delayed extends Comparable<Delayed>
{
    long getDelay(TimeUnit unit);
}

其中getDelay方法返回对象的残留延迟。负值表示延迟已经结束。元素只有在延迟用完的情况下才能从DelayQueue移除。还必须实现compareTo方法。DelayQueue使用该方法对元素进行排序。

5、LinkedTransferQueue实现了TransferQueue接口,该接口允许生产者线程等待,直到消费者准备就绪可以接收一个元素。如果生产者调用

q.transfer(item);

这个调用会阻塞,直到另一个线程将元素删除。

七、线程安全的集合

java.util.concurrent包提供了映射,有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。

这些集合使用复杂的算法,通过允许并发的访问数据结构的不同部分来使竞争极小化。

任何集合类都可以通过使用同步包装器变成线程安全的。

八、执行器

执行器类有许多静态工厂方法用来构建线程池。

执行者工厂方法:

1、newCachedThreadPool:必要时创建线程;空闲线程会被保留60秒。

2、newFixedThreadPool:该池包含固定数量的线程;空闲线程会一直被保留。

3、newSingleThreadExecutor:只有一个线程的“池”,该线程顺序执行每一个提交的任务。

4、newScheduledThreadPool:用于预定执行而构建的固定线程池,替代java.util.Timer。

5、newSingleThreadScheduledExecutor:用于预定执行而构建的单线程“池”。

ScheduledExecutorService接口具有为预定执行或重复执行任务而设计的方法。它是一种允许使用线程池机制的java.util.Timer的泛化。

原文地址:https://www.cnblogs.com/libinhyq/p/12376529.html