• New:新建
  • Runnable:就绪
  • Running:运行
  • Blocked:阻塞
  • Dead:死亡

五状态模型是操作系统教材的视角;Java 层面真正用于排障的是 Thread.State 枚举的六状态。两套模型的映射关系、以及每个阻塞状态对应的排查手段,才是这个知识点的实战价值。

Java 的六状态与五状态映射

Thread.State 含义 对应五状态
NEW 已 new 未 start New
RUNNABLE 可运行/运行中(含等 CPU 与等 IO 的”假运行”) Runnable + Running
BLOCKED 等监视器锁(synchronized 竞争失败) Blocked
WAITING 无限期等待他线程动作(wait/join/park) Blocked
TIMED_WAITING 带超时的等待(sleep(t)/wait(t)/parkNanos) Blocked
TERMINATED 执行结束或异常退出 Dead

关键差异:Java 把”就绪”与”运行”合并为 RUNNABLE——因为 JVM 无法区分线程是在跑还是在等 CPU 调度(那是 OS 的事)。更反直觉的是:Linux 上阻塞于磁盘 IO 的线程在 JVM 里仍显示 RUNNABLE(内核态等待不计入 Java 状态),所以”RUNNABLE 但接口慢”往往要往 IO 方向查。

状态迁移与触发 API

  • NEW → RUNNABLE:start()(注意:重复 start 抛 IllegalThreadStateException)
  • RUNNABLE → BLOCKED:进入 synchronized 块/方法竞争锁失败
  • RUNNABLE → WAITING:Object.wait()、Thread.join()、LockSupport.park()
  • RUNNABLE → TIMED_WAITING:Thread.sleep(t)、wait(t)、join(t)、parkNanos
  • WAITING/BLOCKED → RUNNABLE:被 notify/中断/获得锁/目标线程结束
  • 任意 → TERMINATED:run() 正常返回或异常抛出

排障视角:线程 dump 里的状态读法

jstack 输出中每个线程带状态标注,对应典型问题:

  • 大量 BLOCKED 在同一地址 → 锁竞争热点(找持锁线程在干什么:长事务?IO 在锁内?)
  • 大量 WAITING 在 getTask/await → 线程池空闲或队列空,正常;若业务线程 WAITING 在 join/park 不醒 → 协作方死了,查配对生命周期
  • TIMED_WAITING 在 sleep → 代码里有显式 sleep(常见于轮询等待,考虑改条件变量)
  • 全部 RUNNABLE 但 CPU 100% → 死循环或自旋(CAS 重试风暴)
  • RUNNABLE 但 CPU 低、接口慢 → 内核态 IO 等待(数据库/网络),Java 状态看不见,需配合 netstat/慢查询日志

死亡与复活

线程 TERMINATED 后不可重启(再次 start 抛异常);线程池里”线程因任务异常死亡”由池补充新 Worker(线程对象是新的,不是复活)。理解这一点能解释”线程池里的线程换了但池还在”的现象。

中断:状态之外的协作信号

中断(interrupt)不直接改变状态,而是设置标志位:阻塞在 wait/sleep/join 的线程收到中断抛 InterruptedException 并清标志;RUNNABLE 线程需自行检查 isInterrupted() 响应。优雅停止线程的标准姿势是”中断 + 循环检查”,而非已废弃的 stop()(强制释放锁会破坏不变量)。

五状态是概念模型,六状态是排障工具:BLOCKED 指向锁、WAITING 指向协作、TIMED_WAITING 指向定时、RUNNABLE 的两种隐藏形态(等 CPU/等 IO)指向性能分析。把状态与排查动作绑定记忆,生命周期就不再是背诵题。