Thread
线程的基本概念我们已经在深入理解Java并发机制(1)–理论基础中总结过了,这里不再赘述。
线程的状态
Java线程在其生命周期里处于以下6种状态,这些状态定义在Thread类内部枚举中。
状态 | 说明 |
---|---|
NEW | 初始态,还未调用start()方法 |
RUNNABLE | 运行态,将操作系统中的就绪态和运行态统一定义为RUNNABLE |
BLOCKED | 阻塞态,线程等待获取锁 |
WAITING | 等待态,线程等待其他线程做出一些特定状态(通知或中断) |
TIME_WAITING | 超时等待态,不同于等待态,超时后自行返回 |
TERMINATED | 终止态,线程执行完毕 |
状态转移图如下:
从本质上讲,BLOCKED/WAITING/TIME_WAITING这三种状态都是等待状态,只不过分为三类便于管理。BLOCKED状态是获取锁而不得,WAITING状态是自己放弃执行等待某条件成熟。在需要竞争锁的场景下,这两者分别是阻塞队列和等待队列,阻塞队列竞争锁执行,等待队列等待条件成熟移至阻塞队列竞争锁继续执行。在不需要竞争锁的情况下,WAITING状态可以直接转为RUNNABLE。
阻塞与中断
当线程阻塞时,它通常处于BLOCKED/WAITING/TIME_WAITING状态,等待某个事件的发生(如:IO/锁的获取/sleep中醒来等)。线程阻塞和运行时间长的程序区别在于,阻塞的线程必须等待一个不受它自己控制的事件发生以后才能继续执行。
当一个方法抛出InterruptedException时,表示该方法是一个阻塞方法,如果这个方法被中断,那么它将尽力提前结束阻塞状态。
中断是线程的标志位属性,表示一个运行中的线程是否被其他线程进行了中断操作。中断是一种协作机制,当线程A中断线程B时,仅仅是要求B在执行到可以暂停的地方停止正在执行的工作—这经常用于取消某个操作,方法对中断的响应度越高,就越容易取消那些执行时间很长的操作。
当在代码中调用了一个将抛出InterruptedException的方法时,调用者也就变成了一个阻塞方法,重点是必须要对中断做相应的处理,一般有两种处理方式:
- 传递InterruptedException:不做任何处理将中断情况传递给高层调用者,交由调用者处理
- 恢复中断:当不能抛出InterruptException时,如Runnable中的run方法,捕获到了InterruptException后,调用线程的interrupt方法重新设置中断标志位,以便保持中断信息,供其他代码使用。
这两种方式对于中断的处理都是一个原则:不能无缘无故吃掉中断信息,保证中断信息可靠有效的传递给需要的代码。
取消与关闭
当我们启动一个线程后,如果我们对它失去控制将是一件很可怕的事情,想象一下当一个写文件的线程启动后,我们无法终止它,即使我们不想它继续写了,这很尴尬,在线程中加入控制手段是非常有必要的。
Java没有提供任何机制来安全的终止线程,但提供了线程协作机制—中断,通过中断,一个线程可以终止另外一个线程。
这里说终止,并不意味着线程A可以直接关闭线程B,而是指的是A可以通过interrupt(Thread t)方法去设置线程B的中断标志位。在线程B内有轮询代码(可中断的阻塞方法或者是外层循环代码)不断检查其中断标志位,如果发现已经被线程A中断,是抛出Interrupt-Exception,还是执行其他业务逻辑,还是执行相应的清理工作提前结束线程工作,取决于具体的逻辑需要。
Runnable与Callable
Java线程执行内容的定义有三种方式:
- 继承Thread类,重写run()方法
- 实现Runnable接口
- 实现Callable接口
虽然有三种定义方式,但是本质上线程的启动都是通过Thread实例中的start方法来启动的(线程池的部分我们后面再讲)。
Runnable和Callable的区别和联系:
- 区别:主要表现在两点:1、对异常的捕捉 2、线程执行结果的获取
- 联系:Callable是对Runnable的补充,通过FutureTask类封装为Runnable类型
区别很明显,直接看代码:
Runnable:
public
interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to