java中wait()必须用while循环检查条件而非if,因存在虚假唤醒(spurious wakeup)——线程可能无通知自行苏醒,导致条件未满足就继续执行;while确保每次唤醒后重新验证条件,只有真正满足时才退出循环。

Java中用wait()时发生虚假唤醒(Spurious Wakeup),不是因为notify()或notifyAll()被调用,而是线程在没有明确唤醒信号的情况下自行苏醒。它不违反JVM规范,但会导致条件未满足就继续执行,引发逻辑错误。解决方法不是避免它,而是**始终用while循环检查等待条件是否真正成立**。
为什么不能用 if 判断
if只检查一次,一旦线程被虚假唤醒,就直接往下走,跳过条件重验。比如生产者-消费者场景中,消费者wait()等待队列非空,若被虚假唤醒却没检查队列是否真有元素,就会尝试取空队列,抛出异常或逻辑错乱。
while 循环的正确写法
把等待逻辑包裹在while中,每次唤醒后都重新判断条件。只有条件为true才退出循环,否则继续wait():
✔ 正确示例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
synchronized (lock) {
while (!conditionMet()) { // 条件不满足就一直等
lock.wait();
}
// 此处 conditionMet() 一定为 true
doSomething();
}
✘ 错误写法:
synchronized (lock) {
if (!conditionMet()) { // 只判断一次,无法应对虚假唤醒
lock.wait();
}
doSomething(); // 可能 conditionMet() 仍为 false!
}
conditionMet() 必须是可变、可同步的共享状态
循环判断的条件必须基于多个线程可见且受同一锁保护的变量,例如:
- 使用
volatile修饰的布尔标志(需配合synchronized保证原子性,仅volatile不够) - 检查集合大小:
queue.isEmpty()(前提是queue的操作都在synchronized(lock)块内) - 读取计数器:
count (<code>count由同步代码更新)
关键点:条件检查和wait()必须在同一个synchronized块中,且锁对象一致。
notify 和 notifyAll 的选择影响不大
虚假唤醒与用notify()还是notifyAll()无关,它是底层操作系统或JVM调度导致的。即使只唤醒一个线程,该线程也必须用while重检条件;而notifyAll()唤醒多个线程时,更需要while防止多个线程争抢资源后条件已失效。所以无论哪种唤醒方式,while都是必需的防御手段。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










