Java 线程有哪些状态?BLOCKED 和 WAITING 有什么区别?
简化版
Java 线程有 6 个状态(Thread.State 枚举):NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。核心区别:BLOCKED 是在等一把 synchronized 锁(被动,抢锁失败);WAITING 是主动挂起等别人唤醒(如 wait()、join()),且通常已经释放了锁。
详细版
六个状态各自的含义:
- NEW:
new Thread()了但还没start()。 - RUNNABLE:可运行——它包含「正在 CPU 上执行」和「具备执行资格、等待调度」;Java 状态并不逐一对应操作系统状态。在 HotSpot 的线程转储中,某些本地 I/O 等待也可能显示为 RUNNABLE。
- BLOCKED:想进
synchronized块,但锁被别人拿着,被动阻塞在门口等锁。 - WAITING:调用了无超时的
Object.wait()、Thread.join()、LockSupport.park(),主动无限期等待,直到被唤醒。 - TIMED_WAITING:带超时版本,
sleep(n)、wait(n)、join(n)、parkNanos(),等到超时自动醒。 - TERMINATED:
run()执行完毕,线程结束。
BLOCKED vs WAITING 是重点:前者在等锁(进不了同步块),后者在等信号/条件(拿到锁后主动让出等待)。排查线程 dump 时,先分清线程是「卡在抢锁」还是「在等条件」,定位方向完全不同。
完整版教学
一、状态流转图
start() 抢到锁 / 被 notify / join 结束 / 超时
NEW ───────────► RUNNABLE ◄──────────────────────────────────┐
│ │ │ │
进 synchronized│ │ │wait()/join()/park() 抢 synchronized 锁失败
但锁被占 │ │ └──────────► WAITING ───────────┐ │
│ └─────────► TIMED_WAITING(带超时) ──┤ │
▼ │ │
BLOCKED ◄─────────────────────────────────┘────┘
│ run() 结束
▼
TERMINATED
关键:超时或等待条件满足后,线程还要重新获得执行资格;wait() 的线程尤其需要先重新竞争监视器,不能把「被通知」理解成已经进入临界区。
二、BLOCKED 和 WAITING 的本质区别
| BLOCKED | WAITING | |
|---|---|---|
| 触发 | 抢 synchronized 锁失败 | 主动调 wait()/join()/park() |
| 主被动 | 被动(想进进不去) | 主动(自己让出) |
| 持锁情况 | 还没拿到锁 | 通常已释放锁(wait 会释放) |
| 怎么恢复 | 锁被释放后自己去抢 | 被 notify/unpark/目标线程结束唤醒 |
说白了:BLOCKED 是「求锁而不得」,WAITING 是「得锁后主动歇着等条件」。
三、wait() 为什么必须在循环里检查条件
这是这道题最容易被追问的点。wait() 有三个铁律:
- 必须持有该对象的锁才能调
wait()(否则抛IllegalMonitorStateException); wait()会释放锁并进入 WAITING(这正是它和sleep的本质区别——sleep不释放锁);- 被唤醒后要重新抢锁,抢到才继续。
而条件判断必须用 while 不能用 if:
synchronized (lock) {
while (!conditionMet()) { // 必须 while,不能 if
lock.wait();
}
doWork();
}
原因有二:虚假唤醒(spurious wakeup,线程可能没被 notify 就醒了);以及 notifyAll 唤醒多个线程时,你醒来抢到锁的瞬间,条件可能又被别的线程改回不满足了。所以醒来后必须重新检查条件,while 才能保证这一点。
四、RUNNABLE 的认知误区
很多人以为 RUNNABLE = 正在 CPU 上执行,其实它是个「大口袋」:既包括就绪等调度和正在运行,也可能包括 HotSpot 映射为 RUNNABLE 的某些本地 I/O 等待。具体表现与 JVM 实现和调用路径有关,所以看到线程 dump 里一堆 RUNNABLE 不能直接判定 CPU 忙,必须继续看栈、CPU 时间和多次快照。
五、Java 状态和操作系统状态不是一一对应
Thread.State 是 Java API 的六态抽象,不是操作系统调度器状态的原样暴露。RUNNABLE 表示线程正在 JVM 中执行或具备执行资格,HotSpot 中某些本地 I/O 等待也可能显示为 RUNNABLE,因此不能看到 RUNNABLE 就断定它正在消耗 CPU。
假设线程转储里有 200 个 RUNNABLE,进程 CPU 却只有 5%,更可能是大量线程在本地调用或等待系统资源;若 CPU 为 800%(8 核满载),再用连续栈样本或 JFR 找反复出现的热点栈。状态只能分类,不能代替性能证据。
| Java 状态 | 常见触发 | 是否带期限 |
|---|---|---|
| BLOCKED | 等待进入 synchronized | 否 |
| WAITING | wait()、join()、park() 无期限形式 | 否 |
| TIMED_WAITING | sleep、带超时 wait/join/park | 是 |
| RUNNABLE | 执行、就绪或部分本地等待 | 不适用 |
六、中断不会自动把所有等待都变成终止
中断是协作信号,不是强制杀线程。sleep/wait/join 被中断时会抛 InterruptedException 并清除中断状态;LockSupport.park 返回时不会抛该异常,调用者要检查状态;等待进入 synchronized 的 BLOCKED 线程也不能靠普通中断直接退出等锁。
try {
queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 无法完成本层取消时恢复状态
return;
}
是否恢复中断要看当前层是否消费了取消语义。无条件吞掉异常会让上层无法停止任务,而无条件重新中断也可能让某些循环立即反复失败。
记忆钩子:线程状态回答“此刻为什么没继续”,中断回答“别人请求它停止或醒来”;两套机制相关但不等价。
七、线上线程快照要看时间序列
单次快照看到 WAITING 可能只是线程池工作线程正常空闲。每隔 5 秒抓取 3 次,若同一线程始终 BLOCKED 在同一监视器且 owner 执行慢调用,才更能说明锁竞争或卡死。
诊断时把状态、栈顶、锁拥有者、CPU 采样和业务延迟放到同一时间轴。死锁检测能找环,但不能自动判断“锁内远程调用 30 秒”这类没有环的长阻塞。
八、常见误区与追问
- 误区:RUNNABLE 就代表线程此刻正在占用 CPU。 它还可能处于就绪或 JVM 实现映射出的本地资源等待,需要结合 CPU 与栈判断。
- 误区:
sleep()会释放当前持有的监视器。 sleep 只让当前线程定时等待,不自动释放已经持有的锁。 - 误区:
notify()后等待线程立刻进入 RUNNABLE 执行业务。 它要先从 wait set 转出并重新竞争监视器,可能先处于 BLOCKED。 - 追问:BLOCKED 与 WAITING 的核心区别是什么? BLOCKED 专指等待进入 synchronized 监视器,WAITING 是主动进行无期限等待操作。
- 追问:为什么 wait 条件必须用 while? 线程可能虚假唤醒,且从通知到重新获锁期间条件可能被其他线程改变。
- 追问:TERMINATED 线程能再次 start 吗? 不能,Thread 实例生命周期结束后不可重启。
- 追问:中断 BLOCKED 线程能让它退出 synchronized 等待吗? 普通监视器获取不可中断;需要可中断等锁应选择
lockInterruptibly等方案。
九、加强记忆
六态按生命周期串起来:NEW 经 start 进入 RUNNABLE,争抢 synchronized 失败是 BLOCKED,无期限 wait/join/park 是 WAITING,带期限的 sleep/wait/join/park 是 TIMED_WAITING,执行结束是 TERMINATED。RUNNABLE 是 JVM 抽象,不等于正在吃 CPU;notify 也只让等待者重新竞争锁。wait 要在持锁条件下用 while 检查谓词,中断则是协作取消信号,不会强杀线程或打断所有阻塞。线上必须看多次快照、锁拥有者与 CPU 时间线,不能凭一个状态名下结论。