← 返回题目列表

Java 线程有哪些状态?BLOCKED 和 WAITING 有什么区别?

高频 中等 第 12 / 31 题 更新于 2026/07/25
线程状态并发wait

简化版

Java 线程有 6 个状态(Thread.State 枚举):NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。核心区别:BLOCKED 是在等一把 synchronized 锁(被动,抢锁失败);WAITING主动挂起等别人唤醒(如 wait()join()),且通常已经释放了锁。

详细版

六个状态各自的含义:

  • NEWnew 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(),等到超时自动醒。
  • TERMINATEDrun() 执行完毕,线程结束。

BLOCKED vs WAITING 是重点:前者在等锁(进不了同步块),后者在等信号/条件(拿到锁后主动让出等待)。排查线程 dump 时,先分清线程是「卡在抢锁」还是「在等条件」,定位方向完全不同。

完整版教学

一、状态流转图

        start()              抢到锁 / 被 notify / join 结束 / 超时
NEW ───────────► RUNNABLE ◄──────────────────────────────────┐
                 │  │  │                                        │
    进 synchronized│  │  │wait()/join()/park()          抢 synchronized 锁失败
    但锁被占       │  │  └──────────► WAITING ───────────┐    │
                 │  └─────────► TIMED_WAITING(带超时) ──┤    │
                 ▼                                       │    │
              BLOCKED ◄─────────────────────────────────┘────┘
                 │ run() 结束

             TERMINATED

关键:超时或等待条件满足后,线程还要重新获得执行资格;wait() 的线程尤其需要先重新竞争监视器,不能把「被通知」理解成已经进入临界区。

二、BLOCKED 和 WAITING 的本质区别

BLOCKEDWAITING
触发synchronized 锁失败主动调 wait()/join()/park()
主被动被动(想进进不去)主动(自己让出)
持锁情况还没拿到锁通常已释放锁(wait 会释放)
怎么恢复锁被释放后自己去抢notify/unpark/目标线程结束唤醒

说白了:BLOCKED 是「求锁而不得」,WAITING 是「得锁后主动歇着等条件」

三、wait() 为什么必须在循环里检查条件

这是这道题最容易被追问的点。wait() 有三个铁律:

  1. 必须持有该对象的锁才能调 wait()(否则抛 IllegalMonitorStateException);
  2. wait()释放锁并进入 WAITING(这正是它和 sleep 的本质区别——sleep 不释放锁);
  3. 被唤醒后要重新抢锁,抢到才继续。

而条件判断必须用 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
WAITINGwait()join()park() 无期限形式
TIMED_WAITINGsleep、带超时 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 时间线,不能凭一个状态名下结论。