1.线程生命周期模型
1.1.通用的线程生命周期模型
五态模型:初始状态、可运行状态、运行状态、休眠状态、终止状态
- 初始状态:指的是线程被创建,还不能运行分配CPU执行,这个状态属于编程语言特有的,这里的创建只是指编程语言层面,操作系统层面真正的线程还没有创建
- 可运行状态:指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行
- 运行状态:被分配到CPU的线程的状态
- 休眠状态:运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),就变成了休眠状态,该状态永远没有机会获得CPU的使用权。等待的事件出现后,则会从休眠状态转化到可运行状态
- 终止状态:线程执行完成或出现异常。终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
这五种不同的状态在不同的编程语言中会有简化合并。例如Java中将可运行状态和运行状态合并了,这两个状态在操作系统调度层面有用,而JVM并不关系这两种状态,因为JVM将线程的调度交给操作系统处理了。Java还细化了休眠状态
1.2.Java中线程的生命周期

Java中的线程有六种状态:
- NEW(初始化状态)
- RUNNABLE(可运行/运行状态)
- BLOCKED(阻塞状态)
- WAITING(无时限等待)
- TIMED_WAITING(有时限等待)
- TERMINATED(终止状态)
简化为如下图:
2.线程状态的转换
2.1.RUNNABLE与BLOCKED的状态转换
只有一种场景会触发:线程等待sychronized的隐式锁。synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待。等待到可执行后,又会从BLOCKED状态切换为RUNNABLE状态。
调用阻塞式API时,线程是否会转换到BLOCKED状态吗:
- 操作系统层面,线程会转化到休眠状态
- JVM层面,Java线程的状态不会发生任何改变,状态会依然保持 RUNNABLE 状态。因为在JVM看来,线程等待CPU的使用权(可执行状态)和等待IO(操作系统处于休眠状态)没有区别,都是等待资源。所以都归入了RUNNABLE状态
而我们平时所谓的 Java 在调用阻塞式 API 时,线程会阻塞,指的是操作系统线程的状态,并不是 Java 线程的状态
2.2.RUNNABLE与WAITING的状态转换
三种场景会触发:
- 获得synchronized隐式锁的线程,调用无参数的Object.wait()方法
- 调用无参数的Thread.join()。join()是一个线程同步的方法,例如有一个Thread A,当调用A.join()的时候,执行这条语句的线程会等待thread A执行完,而等待的线程会从RUNNABLE转到WAITING,A线程执行完后,等待的线程又转回去
- 调用LockSupport.park()方法,Java 并发包中的锁,都是基于LockSupport实现的。调用 LockSupport.park() 方法,当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 WAITING 状态转换到 RUNNABLE
2.3.RUNNABLE与TIMED_WAITING的状态转换
有五种场景会触发这种转换:
- 调用带超时参数的 Thread.sleep(long millis) 方法
- 获得 synchronized 隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法;
- 调用带超时参数的 Thread.join(long millis) 方法;
- 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
- 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。
相较于WAITING,这里只是多了超时参数
2.4.从NEW到RUNNABLE状态转换
Java 刚创建出来的 Thread 对象就是 NEW 状态,创建Thread对象有两种办法:
-
继承Thread对象,重写run()方法
-
实现Runnable接口,重写run()方法,并将该实现类作为创建 Thread 对象的参数
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());
NEW到RUNNABLE只需要调用Thread对象的start()方法
2.5.从 RUNNABLE 到 TERMINATED状态转换
有两种场景:
- 执行完run()方法,或抛出异常
- Thread.stop()方法。不过该方法已废弃,可用interrupt() 方法替代
那 stop() 和 interrupt() 方法的主要区别是什么呢?:
-
stop()会直接干掉线程,可能会出现不执行解锁操作的情形。例如:如果线程持有ReentrantLock锁,被 stop() 的线程并不会自动调用 ReentrantLock 的 unlock() 去释放锁,那其他线程就再也没机会获得 ReentrantLock 锁,因此不建议使用。似的方法还有 suspend() 和 resume() 方法,这两个方法同样也都不建议使用了
-
interrupt()方法的仅仅是通知,线程同时可以无视这个通知。接受通知有两种形式,1.异常 2.主动探测
-
异常:
线程处于WAITING、TIMED_WAITING 状态时,其他线程调用线程A的interrupt()方法。会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。我们看这些方法的签名,发现都会 throws InterruptedException 这个异常。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。
线程处于RUNNABLE状态时,并且阻塞在 java.nio.channels.InterruptibleChannel 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 会触发 java.nio.channels.ClosedByInterruptException 这个异常;而阻塞在 java.nio.channels.Selector 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 的 java.nio.channels.Selector 会立即返回。
-
主动探测
线程 A 可以通过 isInterrupted() 方法,检测是不是自己被中断了