Lesson 11 · 并发编程
线程生命周期:6 种状态与 12 次转换
从一道面试题开始
面试官双手交叉,不紧不慢地抛出这个问题:
候选人 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()分别进入什么状态?
带着这些问题,我们开始今天的线程之旅。
Thread.State 枚举:JDK 源码怎么说
很多人背过线程状态,但很少有人翻开 JDK 源码看一看。线程的所有状态定义在 java.lang.Thread.State 这个枚举中:
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.
}
注意两个容易被忽略的细节:
ReentrantLock.lock(),线程进入的是 WAITING 或 TIMED_WAITING(底层走 LockSupport.park),而不是 BLOCKED。六种状态逐一拆解
| 状态 | 含义 | 进入方式(代码示例) | 退出条件 |
|---|---|---|---|
| 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 |
| TERMINATED | run() 方法执行完毕 | run() 正常返回或抛出未捕获异常 | 终态,不可再转换 |
下面用代码验证每个状态:
// 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
完整状态机:6 个状态 × 12 次转换
下面这张图是线程生命周期的完整状态机。每一条箭头对应一次状态转换,箭头上标注了触发的 API。建议你放大仔细看——面试时能画出这张图,基本满分。
将 12 次转换整理成表格:
| # | 转换 | 触发 API |
|---|---|---|
| ① | NEW → RUNNABLE | Thread.start() |
| ② | RUNNABLE → BLOCKED | 进入 synchronized 块,锁被其他线程持有 |
| ③ | BLOCKED → RUNNABLE | 成功获取到监视器锁 |
| ④ | RUNNABLE → WAITING | Object.wait() / LockSupport.park() / Thread.join() |
| ⑤ | WAITING → BLOCKED | notify() / notifyAll(),唤醒后需重新竞争锁 |
| ⑥ | WAITING → RUNNABLE | LockSupport.unpark(),无需竞争锁直接进入就绪 |
| ⑦ | RUNNABLE → TIMED_WAITING | Thread.sleep(ms) / Object.wait(ms) / Thread.join(ms) / LockSupport.parkNanos() |
| ⑧ | TIMED_WAITING → RUNNABLE | 超时自动恢复 / 被 unpark() 唤醒 |
| ⑨ | TIMED_WAITING → BLOCKED | Object.wait(ms) 超时后需重新竞争 synchronized 锁 |
| ⑩ | RUNNABLE → TERMINATED | run() 方法正常返回或抛出未捕获异常 |
| ⑪ | BLOCKED → TERMINATED | Thread.interrupt()(阻塞中的线程被中断) |
| ⑫ | WAITING → TERMINATED | Thread.interrupt()(等待中的线程被中断) |
BLOCKED vs WAITING:最容易混淆的一对
这是面试中区分度最高的追问。很多候选人把二者混为一谈,实际上它们的本质完全不同:
WAITING 是"主动等待"——我自己调了 wait() / park(),选择放弃执行权,等别人通知我。
用一张表对比:
| 维度 | BLOCKED | WAITING |
|---|---|---|
| 触发方式 | 自动(JVM 检测到锁不可用) | 手动调用 API |
| 等待的东西 | synchronized 监视器锁 | 另一个线程的信号 |
| 谁来唤醒 | 持有锁的线程释放锁后,JVM 自动唤醒 | 其他线程调用 notify() / unpark() |
| 超时 | 不支持(无限等待) | 有 TIMED_WAITING 版本 |
| ReentrantLock | 不产生 BLOCKED! | lock() → WAITING(底层走 park) |
代码对比——同样"等待获取资源",产生的状态完全不同:
// ━━━ 场景 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(); }
});
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.State | Java 层面的线程状态 | 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":
# → 恭喜,确认死锁了。根据循环依赖链修复代码。
总结与面试速查表
把今天的核心知识压缩成一张速查表:
| 状态 | 一句话 | 关键 API(进入) | 关键 API(退出) |
|---|---|---|---|
| NEW | 创建了但没 start | new Thread() | start() |
| RUNNABLE | 正在跑或排队等 CPU | start() / 从其他状态恢复 | 阻塞 / 等待 / 结束 |
| BLOCKED | 等 synchronized 锁 | 进入 sync 块但锁被占用 | 拿到锁 / 被中断 |
| WAITING | 等别人通知 | wait() / park() / join() | notify / unpark / 中断 |
| TIMED_WAITING | 等一会儿,到点自动醒 | sleep(ms) / wait(ms) | 超时 / 被唤醒 / 中断 |
| TERMINATED | 跑完了,不可逆 | run() 结束 | 无 |
高频面试追问 & 标准答案:
A: 三个维度——① sleep() 是 Thread 的静态方法,wait() 是 Object 的实例方法;② sleep() 不释放锁(TIMED_WAITING),wait() 释放锁(WAITING);③ sleep() 到时间自动醒,wait() 需要被 notify / notifyAll 唤醒。
A: 不一定。被 notify 后线程从 WAITING 变为 BLOCKED(因为需要重新竞争监视器锁),只有成功拿到锁后才能变为 RUNNABLE 继续执行。如果此时有其他线程持有该锁,就会继续在 BLOCKED 状态排队。
A: 不能。第二次调用会抛 IllegalThreadStateException。因为线程状态已经从 NEW 变为 RUNNABLE(或更后面的状态),start() 方法内部会检查 threadStatus != 0(0 代表 NEW),非 0 直接抛异常。这也是为什么线程是一次性的——TERMINATED 后不能重新启动。
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 是生产环境分析线程状态的第一工具