Lesson 11 · 并发编程

线程生命周期:6 种状态与 12 次转换

中级·#并发·#基础

第 1 站

从一道面试题开始

面试官双手交叉,不紧不慢地抛出这个问题:

"说说线程的生命周期?它有哪些状态?状态之间怎么转换?"

候选人 A 脱口而出:"有 NEW、RUNNING、BLOCKED、WAITING、TERMINATED 五种"。面试官微微一笑——漏了一个。

候选人 B 胸有成竹:"六种!NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED"。面试官追问:那状态之间的 12 次转换分别由什么 API 触发?候选人沉默了。

这道题之所以高频,是因为它同时考察了三件事:

  • 你是否真正读过 Thread.State 的源码
  • 你是否理解每种状态背后的操作系统调度原理
  • 你能否把状态转换具体 API 一一对应

发车之前,先问自己三个问题:

  • RUNNABLE 到底包不包括"正在等待 CPU 时间片"的线程?
  • BLOCKED 和 WAITING 的本质区别是什么?
  • Thread.sleep()Object.wait() 分别进入什么状态?

带着这些问题,我们开始今天的线程之旅。

第 2 站

Thread.State 枚举:JDK 源码怎么说

很多人背过线程状态,但很少有人翻开 JDK 源码看一看。线程的所有状态定义在 java.lang.Thread.State 这个枚举中:

java/lang/Thread.java(JDK 17)
public enum State {
    NEW,            // Thread object created but start() not yet called.
    RUNNABLE,       // Executing in the JVM — may be waiting for OS resources (e.g. CPU).
    BLOCKED,        // Waiting to acquire a monitor lock (synchronized block/method).
    WAITING,        // Waiting indefinitely for another thread's action (wait, park, join).
    TIMED_WAITING,  // Waiting up to a specified time (sleep, wait(ms), parkNanos).
    TERMINATED      // The thread has completed execution.
}

注意两个容易被忽略的细节:

细节 1RUNNABLE 不等于"正在执行"。JDK 注释明确写了 "may be waiting for OS resources such as processor time"——也就是说,正在等待 CPU 时间片的线程,状态也是 RUNNABLE。Java 没有单独的 READY / RUNNING 区分。
细节 2BLOCKED 专指等待 synchronized 监视器锁。如果你用的是 ReentrantLock.lock(),线程进入的是 WAITING 或 TIMED_WAITING(底层走 LockSupport.park),而不是 BLOCKED。
记住Thread.State 只有 6 个值,不是 5 个也不是 7 个。面试时说"5 种"直接扣分。
第 3 站

六种状态逐一拆解

状态含义进入方式(代码示例)退出条件
NEW对象已创建,尚未调用 start()Thread t = new Thread();调用 t.start()
RUNNABLE正在运行 或 等待 CPU 调度t.start()、从阻塞返回获取不到锁 / 主动等待 / 执行完毕
BLOCKED等待获取 synchronized 监视器锁进入 synchronized 块但锁被其他线程持有获取到锁 → RUNNABLE
WAITING无限期等待另一个线程的动作Object.wait()LockSupport.park()Thread.join()被 notify / unpark → RUNNABLE 或 BLOCKED
TIMED_WAITING限时等待,到时间自动恢复Thread.sleep(ms)Object.wait(ms)LockSupport.parkNanos()超时 / 被唤醒 → RUNNABLE 或 BLOCKED
TERMINATEDrun() 方法执行完毕run() 正常返回或抛出未捕获异常终态,不可再转换

下面用代码验证每个状态:

ThreadStateDemo.java
// 1. NEW
Thread t1 = new Thread(() -> {});
System.out.println(t1.getState()); // NEW

// 2. RUNNABLE
Thread t2 = new Thread(() -> {
    while (true) { Thread.yield(); }
});
t2.start();
System.out.println(t2.getState()); // RUNNABLE

// 3. BLOCKED
Object lock = new Object();
Thread holder = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(999999); } catch (Exception e) {}
    }
});
holder.start();
Thread.sleep(100); // 等 holder 拿到锁
Thread t3 = new Thread(() -> {
    synchronized (lock) {} // 拿不到锁 → BLOCKED
});
t3.start();
Thread.sleep(100);
System.out.println(t3.getState()); // BLOCKED

// 4. WAITING
Thread t4 = new Thread(() -> {
    synchronized (lock) {
        try { lock.wait(); } catch (Exception e) {}
    }
});
// ... start + sleep → WAITING

// 5. TIMED_WAITING
Thread t5 = new Thread(() -> {
    try { Thread.sleep(60000); } catch (Exception e) {}
});
t5.start();
Thread.sleep(100);
System.out.println(t5.getState()); // TIMED_WAITING

// 6. TERMINATED
Thread t6 = new Thread(() -> {});
t6.start();
t6.join();
System.out.println(t6.getState()); // TERMINATED
核心每种状态都有明确的"入口 API"和"出口条件"。面试不是背名词,而是能说清楚什么代码产生什么状态
第 4 站

完整状态机:6 个状态 × 12 次转换

下面这张图是线程生命周期的完整状态机。每一条箭头对应一次状态转换,箭头上标注了触发的 API。建议你放大仔细看——面试时能画出这张图,基本满分。

Java 线程完整状态机 — 6 状态 · 12 转换 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED ① start() ② 进入 synchronized 块但锁被占用 ③ 获取到锁 ④ Object.wait() LockSupport.park() Thread.join() ⑤ notify/notifyAll 需重新竞争锁 ⑥ unpark/notify(无锁竞争) ⑦ Thread.sleep(ms) Object.wait(ms) LockSupport.parkNanos() Thread.join(ms) ⑧ 超时 / 被唤醒 ⑨ wait(ms) 超时后需重新竞争锁 ⑩ run() 执行完毕 ⑪ 被中断 (interrupt) ⑫ 被中断 初始态 运行态 阻塞态 等待态 限时等待态 终止态
图 1Java 线程完整状态机:6 个状态、12 次转换。每条箭头旁标注了触发转换的 API。

将 12 次转换整理成表格:

#转换触发 API
NEW → RUNNABLEThread.start()
RUNNABLE → BLOCKED进入 synchronized 块,锁被其他线程持有
BLOCKED → RUNNABLE成功获取到监视器锁
RUNNABLE → WAITINGObject.wait() / LockSupport.park() / Thread.join()
WAITING → BLOCKEDnotify() / notifyAll(),唤醒后需重新竞争锁
WAITING → RUNNABLELockSupport.unpark(),无需竞争锁直接进入就绪
RUNNABLE → TIMED_WAITINGThread.sleep(ms) / Object.wait(ms) / Thread.join(ms) / LockSupport.parkNanos()
TIMED_WAITING → RUNNABLE超时自动恢复 / 被 unpark() 唤醒
TIMED_WAITING → BLOCKEDObject.wait(ms) 超时后需重新竞争 synchronized 锁
RUNNABLE → TERMINATEDrun() 方法正常返回或抛出未捕获异常
BLOCKED → TERMINATEDThread.interrupt()(阻塞中的线程被中断)
WAITING → TERMINATEDThread.interrupt()(等待中的线程被中断)
状态机核心公式:状态 = f(最近一次 API 调用)——每次转换都由一个明确的 API 触发,不存在"隐式"转换。
第 5 站

BLOCKED vs WAITING:最容易混淆的一对

这是面试中区分度最高的追问。很多候选人把二者混为一谈,实际上它们的本质完全不同:

BLOCKED 是"被动阻塞"——我想拿锁,但锁在别人手里,JVM 自动帮我排队。
WAITING 是"主动等待"——我自己调了 wait() / park(),选择放弃执行权,等别人通知我。

用一张表对比:

维度BLOCKEDWAITING
触发方式自动(JVM 检测到锁不可用)手动调用 API
等待的东西synchronized 监视器锁另一个线程的信号
谁来唤醒持有锁的线程释放锁后,JVM 自动唤醒其他线程调用 notify() / unpark()
超时不支持(无限等待)有 TIMED_WAITING 版本
ReentrantLock不产生 BLOCKED!lock() → WAITING(底层走 park)

代码对比——同样"等待获取资源",产生的状态完全不同:

BlockedVsWaiting.java
// ━━━ 场景 1:synchronized → BLOCKED ━━━
Object monitor = new Object();

Thread t1 = new Thread(() -> {
    synchronized (monitor) {
        Thread.sleep(10000); // 持锁休眠
    }
});

Thread t2 = new Thread(() -> {
    synchronized (monitor) { // 拿不到锁 → BLOCKED
        System.out.println("拿到了");
    }
});

// ━━━ 场景 2:ReentrantLock → WAITING ━━━
ReentrantLock lock = new ReentrantLock();

Thread t3 = new Thread(() -> {
    lock.lock();
    try {
        Thread.sleep(10000); // 持锁休眠
    } finally { lock.unlock(); }
});

Thread t4 = new Thread(() -> {
    lock.lock(); // 拿不到锁 → WAITING(不是 BLOCKED!)
    try {
        System.out.println("拿到了");
    } finally { lock.unlock(); }
});
底层原因synchronized 的阻塞由 JVM 的 ObjectMonitor 管理,线程排队在 EntryList 中,状态标记为 BLOCKED。而 ReentrantLock 底层基于 AQS + LockSupport.park(),线程被 park 后状态是 WAITING。这是 synchronized 和 ReentrantLock 在实现层面的核心差异之一。
面试金句BLOCKED 是 JVM 帮你排队等锁(自动),WAITING 是你自己选择等待信号(主动)。ReentrantLock.lock() 产生的不是 BLOCKED 而是 WAITING。
第 6 站

jstack 实战:从线程转储中读状态

面试官的终极杀招:"给你一个线程转储,你能分析出什么问题?"掌握 jstack 输出格式,是你从"背八股"进阶到"真会用"的标志。

先看一段真实的 jstack 输出(节选):

jstack 输出示例
$ jstack 12345
Full thread dump OpenJDK 64-Bit Server VM (17.0.2+8):

"worker-1" #11 prio=5 os_prio=0 tid=0x00007f3a1c0e8800 nid=0x2a03
  waiting for monitor entry [0x00007f3a08ffe000]
  java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.OrderService.process(OrderService.java:42)
    - waiting to lock <0x000000076bf62208> (a java.lang.Object)
    at com.example.Worker.run(Worker.java:18)

"worker-2" #12 prio=5 os_prio=0 tid=0x00007f3a1c0ea000 nid=0x2a04
  in Object.wait() [0x00007f3a08efe000]
  java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076bf62208> (a java.lang.Object)
    at java.lang.Object.wait(Object.java:328)
    at com.example.Consumer.consume(Consumer.java:35)

"timer-1" #13 prio=5 os_prio=0 tid=0x00007f3a1c0ec000 nid=0x2a05
  sleeping [0x00007f3a08dfe000]
  java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at com.example.Scheduler.poll(Scheduler.java:22)

"main" #1 prio=5 os_prio=0 tid=0x00007f3a1c010800 nid=0x2901
  runnable [0x00007f3a23ffe000]
  java.lang.Thread.State: RUNNABLE
    at com.example.App.main(App.java:15)

逐行解读的关键字段:

字段含义示例
nid操作系统原生线程 ID(十六进制)0x2a03 → 用 top -Hp 查 CPU 占用
Thread.StateJava 层面的线程状态BLOCKED / WAITING / RUNNABLE ...
waiting for monitor entry正在排队等 synchronized 锁对应 BLOCKED
in Object.wait()调用了 wait() 正在等待信号对应 WAITING
waiting to lock <addr>等待获取的锁对象地址和"locked <addr>"配对分析
- locked <addr>当前持有的锁找到谁持有了这把锁

排查死锁的三板斧:

排查步骤
# 第一步:导出线程转储
jstack -l <pid> > thread_dump.txt

# 第二步:搜索 BLOCKED 状态的线程
grep -A 5 "BLOCKED" thread_dump.txt

# 第三步:根据锁地址找到持有者
grep "0x000000076bf62208" thread_dump.txt
# → 看到 worker-2 执行 wait() 时释放了锁
# → 看到 worker-1 在等这把锁 → 检查是否有死锁

# 如果 jstack 末尾出现 "Found one Java-level deadlock":
# → 恭喜,确认死锁了。根据循环依赖链修复代码。
实战技巧多次执行 jstack 并做 diff,可以识别出"卡住不动"的线程——如果某线程连续三次转储都停在同一行,大概率是死锁或活锁。
工具链jstack → grep BLOCKED → 找锁地址 → 找持有者 → 判断死锁。这是生产环境排查线程问题的标准流程。
第 7 站

总结与面试速查表

把今天的核心知识压缩成一张速查表:

状态一句话关键 API(进入)关键 API(退出)
NEW创建了但没 startnew Thread()start()
RUNNABLE正在跑或排队等 CPUstart() / 从其他状态恢复阻塞 / 等待 / 结束
BLOCKED等 synchronized 锁进入 sync 块但锁被占用拿到锁 / 被中断
WAITING等别人通知wait() / park() / join()notify / unpark / 中断
TIMED_WAITING等一会儿,到点自动醒sleep(ms) / wait(ms)超时 / 被唤醒 / 中断
TERMINATED跑完了,不可逆run() 结束

高频面试追问 & 标准答案:

Q1: sleep() 和 wait() 的区别?

A: 三个维度——① sleep() 是 Thread 的静态方法,wait() 是 Object 的实例方法;② sleep() 不释放锁(TIMED_WAITING),wait() 释放锁(WAITING);③ sleep() 到时间自动醒,wait() 需要被 notify / notifyAll 唤醒。

Q2: 线程从 WAITING 被 notify 后,一定能继续执行吗?

A: 不一定。被 notify 后线程从 WAITING 变为 BLOCKED(因为需要重新竞争监视器锁),只有成功拿到锁后才能变为 RUNNABLE 继续执行。如果此时有其他线程持有该锁,就会继续在 BLOCKED 状态排队。

Q3: start() 能调用两次吗?

A: 不能。第二次调用会抛 IllegalThreadStateException。因为线程状态已经从 NEW 变为 RUNNABLE(或更后面的状态),start() 方法内部会检查 threadStatus != 0(0 代表 NEW),非 0 直接抛异常。这也是为什么线程是一次性的——TERMINATED 后不能重新启动。

Q4: 如何正确中断一个线程?

A: 调用 Thread.interrupt()。如果目标线程处于 sleep / wait / join(TIMED_WAITING 或 WAITING),会立即抛出 InterruptedException 并清除中断标志。如果目标线程正在 RUNNABLE 状态运行,只设置中断标志位,需要目标线程自己检查 Thread.interrupted() 并退出。

本站回顾:
  • Thread.State 有且仅有 6 个值:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED
  • 状态之间有 12 次转换,每次都由明确的 API 触发
  • BLOCKED 是被动等锁(synchronized),WAITING 是主动等信号(wait / park)
  • ReentrantLock.lock() 产生的状态是 WAITING 而不是 BLOCKED
  • jstack 是生产环境分析线程状态的第一工具