必须用while循环包裹wait()调用,以防范虚假唤醒和竞态唤醒;正确写法是synchronized块内while(!condition) wait(),唤醒后重检条件;优先使用notifyall()确保安全性。

在 Java 中,必须用 while 循环包裹 wait() 调用,而不是 if,这是避免虚假唤醒(spurious wakeup)最核心、最有效的做法。虚假唤醒是指线程在没有被显式 notify/notifyAll 的情况下,从 wait() 中意外返回,这在 JVM 规范中是允许的(尤其在 Linux 的 futex 实现或某些平台信号处理机制下)。仅靠 if 判断条件是否满足,会导致线程在条件实际不成立时继续执行,引发数据错乱或逻辑崩溃。
为什么 if + wait() 会出问题?
假设一个生产者-消费者场景,缓冲区为空时消费者调用 wait();若发生虚假唤醒,线程醒来但缓冲区仍为空,此时若用 if 判断,就会跳过检查直接消费,导致 NoSuchElementException 或空指针异常。
更危险的是:即使没发生虚假唤醒,被 notify 的线程醒来后,条件也可能已被其他线程抢先改变(例如多个消费者竞争,第一个消费者消费完后唤醒第二个,但缓冲区已再次变空)。这叫“竞态唤醒”(race wakeup),和虚假唤醒效果相同,都需要用循环重检。
正确写法:while 循环 + wait() + 条件变量
标准模式如下(以共享队列为例):
synchronized (lock) {
while (!conditionIsTrue()) { // ⚠️ 必须是 while,不是 if
try {
lock.wait(); // 可选:wait(timeout) 防永久阻塞
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; // 或抛异常,按业务决定
}
}
// ✅ 此时 conditionIsTrue() 一定为 true,安全执行后续操作
doSomethingWhenConditionMet();
}
-
conditionIsTrue() 是一个明确的布尔表达式,比如
queue.isEmpty()、count 等,不能是模糊判断 - wait() 必须在 synchronized 块内调用,且锁对象与 notify/notifyAll 使用的必须是同一个对象
- 唤醒后重新进入 while 条件判断——既防御虚假唤醒,也应对条件被其他线程修改的情况
notify 还是 notifyAll?
多数情况下推荐用 notifyAll(),除非你 100% 确认:等待的线程只有一种类型、条件互斥、且唤醒一个就足够推进全局状态。
- 用
notify()有风险:可能唤醒了等待不相关条件的线程(比如多个条件共用一把锁),它醒来后因条件不满足又 wait(),而真正需要的线程却一直沉睡 -
notifyAll()虽带来一点性能开销(所有等待线程竞争锁),但语义清晰、不易出错,是更稳妥的选择 - 如果追求极致性能且场景简单(如单生产者单消费者+单一条件),可谨慎使用 notify,但务必配全 while 循环
补充建议:别忽略中断和超时
真实系统中,线程应响应中断,并避免无限等待:
- 捕获
InterruptedException后,通常应恢复中断状态(Thread.currentThread().interrupt()),而非吞掉 - 对关键 wait 可加超时,如
lock.wait(5000),防止因 bug 或死锁导致线程永久挂起 - 超时后仍需检查条件是否满足,因为超时返回 ≠ 条件成立
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











