一、总述
多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务称为一个线程,它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序。
多进程与多线程的区别:每个进程拥有自己的一整套变量,而线程则共享数据。
二、中断线程
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的泛化。