15Java多线程之生命周期和状态
线程生命周期
线程状态
线程的状态转换图及常见执行情况(图片仅供参考,详细状态见后文):
Java 线程的生命周期中,在 Thread 类里有一个枚举类型 State,定义了线程的几种状态,分别有:
Thread.State.NEW;
Thread.State.RUNNABLE;
Thread.State.BLOCKED;
Thread.State.WAITING;
Thread.State.TIMED_WAITING;
Thread.State.TERMINATED;
初始/新建状态 - NEW
Thread.State.NEW
:尚未启动的线程的线程状态。
实现 Runnable 接口和继承 Thread 可以得到一个线程类,new 一个实例出来,线程就进入了初始状态。它保持这个状态直到程序 start() 这个线程。
RUNNABLE
Thread.State.RUNNABLE
:可运行线程的线程状态,包括两种状态:
就绪状态(可执行)
简言之,无执行权但可执行。
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态,正在等待CPU资源。
调用线程的 start() 方法,此线程进入就绪状态。就绪状态的线程处于就绪队列中。
当前线程 sleep() 方法结束,其他线程 join() 结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的 yield() 方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
运行状态(正在执行)
简言之,有执行权且正在执行。
线程调度程序(JVM或操作系统的线程调度器)从可运行池(处于就绪状态的线程)中选择一个线程执行,即就绪状态的线程获取到CPU资源,此时线程就由就绪变成运行。这也是线程进入运行状态的唯一方式。
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态
同步阻塞 - BLOCKED
Thread.State.BLOCKED
:线程被阻塞,等待监视器锁的线程状态。触发条件:synchronized、Lock.lock。
因为线程获取 synchronized 同步锁失败(同步锁被其他线程占用),所以线程阻塞在进入 synchronized 关键字修饰的代码块或方法(在努力获取锁/争夺锁),此时的状态为 BLOCKED
。调用 Object.notify
后重新进入重新获取锁。
等待阻塞
等待 - WAITING
Thread.State.WAITING
:等待状态
调用以下方法之一,线程处于等待状态:
Object.wait
调用无参方法Thread.join
调用无参方法LockSupport.park
Condition.await()
处于这种状态的线程不会被分配 CPU 资源,需主动唤醒,否则会处于无限等待状态。例如:
- 在对象上调用
Object.wait()
的线程正在等待另一个线程在该对象上调用Object.notify()
或Object.notifyAll()
。 LockSupport.unpark
已启动的线程, 如果线程在park上被阻塞,那么它将解除阻塞。 否则,保证它的下一次park调用不会阻塞。- 调用
Thread.join()
线程正在等待指定的线程终止。
Object.wait()
能否使用 LockSupport.unpark
解除阻塞?LockSupport.park
能否使用 Object.notify()
唤醒?
定时等待 - TIMED_WAITING
Thread.State.TIMED_WAITING
:具有指定等待时间的等待状态
调用以下方法之一,线程处于定时等待状态:
Thread.sleep
Object.wait
调用有参方法Thread.join
调用有参方法LockSupport.parkNanos
LockSupport.parkUntil
Condition.await()
调用有参方法
处于这种状态的线程不会被分配 CPU 执行时间,如果不主动唤醒,那么一定时间后它们会自动唤醒。
其他阻塞
发出 I/O 请求时,线程也会进入阻塞状态。但不知实际对应哪一种( Thread.State.BLOCKED
) ?I/O 处理完毕,重新转为就绪状态。
终止/死亡状态 - TERMINATED
Thread.State.TERMINATED
:线程已完成执行或被终止
如何终止正在运行的线程:
- 主线程 main() 方法完成即终止。
- 线程的 run() 方法完成即终止。
- 终止条件发生。调用 stop()、interrupt() 方法