必须用while循环调用wait()以应对虚假唤醒,即线程未被notify却提前返回;需在synchronized块内循环检查共享条件,条件变量须volatile或受同一锁保护,并优先使用notifyall()。

Java 中 wait() 方法必须在 while 循环中调用,而不是 if 语句,这是应对虚假唤醒(spurious wakeup)最根本、最标准的做法。
为什么虚假唤醒会发生
虚假唤醒是指线程在没有被 notify() 或 notifyAll() 显式唤醒的情况下,wait() 提前返回。这并非 JVM Bug,而是底层操作系统(如 Linux 的 futex、pthread_cond_wait)允许的合法行为。JVM 规范明确允许这种现象,因此所有正确使用 wait/notify 的代码都必须容忍它。
正确写法:始终用 while 循环检查条件
等待的条件必须是可变的共享状态(如队列是否为空、资源是否就绪),且每次从 wait() 返回后,都要重新验证该条件是否真正满足:
- 用
while (conditionIsNotMet()) { obj.wait(); },而非if - 条件判断必须和
wait()在同一把锁(synchronized 块)内执行 - 条件变量应为
volatile或受同一锁保护,确保可见性
示例(生产者-消费者中的消费者取数据):
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait(); // 可能虚假唤醒,所以必须循环检查
}
return queue.poll();
}
notify 和 notifyAll 的选择要匹配等待逻辑
虚假唤醒虽不由 notify 类型直接引起,但错误使用会影响条件重检的可靠性:
- 当多个线程等待不同条件(如“有数据”和“有空位”)时,只用
notify()可能唤醒错的线程,导致它因条件不满足而继续 wait——这看起来像“卡住”,实则是逻辑缺陷 - 除非你 100% 确保每次只有一个线程在等同一条件,且
notify()总能唤醒那个线程,否则优先用notifyAll() - 即使用了
notifyAll(),仍需 while 循环——因为所有被唤醒线程都可能发现条件未满足
避免常见陷阱
以下做法会放大虚假唤醒带来的风险:
- 在 synchronized 外检查条件(破坏原子性与可见性)
- 将
wait()放在try-catch中却忽略InterruptedException,或吞掉异常却不恢复中断状态 - 使用非 final 或非受控对象作为锁(如
new Object()是安全的,但this或字符串字面量易引发意外竞争) - 条件判断依赖非 volatile 的普通字段,且无同步保护(其他线程修改后本线程看不到更新)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











