《Java 并发编程实战》09 阅读笔记

235 阅读3分钟

Java 线程的生命周期

通用的线程生命周期可用五种状态描述。

  • 初始状态
    • 指线程在编程语言层面被创建,但在操作系统层面还未被创建。
  • 可运行状态
    • 线程在操作系统层面被创建,可以分配 CPU 执行。
  • 运行状态
    • 被分配到 CPU 的处于可运行状态的线程。
  • 休眠状态
    • 运行状态的线程如果调用一个阻塞的 API,那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
  • 终止状态
    • 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,也就意味着线程的生命周期结束了。

详见通用线程状态转换图。

Java 语言中线程共有六种状态,是对通用的五种状态的合并与细化。

  • NEW(初始化状态)
  • RUNNABLE(可运行 / 运行状态)
  • BLOCKED(阻塞状态)
  • WAITING(无限等待)
  • TIMED_WAITING(有限等待)
  • TERMINATED(终止状态)

其中 BLOCKED、WAITING、TIMED_WAITING 都属于休眠状态。
只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。
BLOCKED、WAITING、TIMED_WAITING 也可以理解为线程导致休眠状态的三种原因。
详见 Java 中的线程状态转换图。

RUNNABLE 与 BLOCKED 的状态转换
synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,这种情况下,等待的线程就会从 RUNNABLE 转换到 BLOCKED 状态。而当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转换到 RUNNABLE 状态。

RUNNABLE 与 WAITING 的状态转换
第一种场景,获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法。 第二种场景,调用无参数的 Thread.join(),等待中的这个线程,其状态会从 RUNNABLE 转换到 WAITING。当线程 thread A 执行完,原来等待它的线程又会从 WAITING 状态转换到 RUNNABLE。 第三种场景,调用 LockSupport.park() 方法,当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 WAITING 状态转换到 RUNNABLE。

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) 方法 TIMED_WAITING 和 WAITING 状态的区别,仅仅是触发条件多了超时参数。

从 NEW 到 RUNNABLE 状态
Java 刚创建出来的 Thread 对象就是 NEW 状态,从 NEW 状态转换到 RUNNABLE 状态只要调用线程对象的 start() 方法就可以了。

从 RUNNABLE 到 TERMINATED 状态
线程执行完 run() 方法后,会自动转换到 TERMINATED 状态。 如果执行 run() 方法的时候异常抛出,也会导致线程终止。 调用 interrupt() 方法强制中断 run() 方法的执行。

诊断并发问题,可使用 jstack 命令或 Java VisualVM 可视化工具。