wait必须在while循环中调用,不能用if,因为虚假唤醒是JVM允许的合法行为,线程可能未被notify就自行醒来;若用if,唤醒后直接执行会导致条件不满足时出错,而while可确保每次唤醒后重新校验条件。

wait 方法必须在 while 循环里调用,不能用 if
虚假唤醒(spurious wakeup)是 wait() 的合法行为:线程可能在没有被 notify() 或 notifyAll() 唤醒的情况下自行醒来。JVM 规范允许这种行为,Linux futex 和部分 JVM 实现确实会触发它。如果只用 if 判断条件后就执行后续逻辑,线程可能在条件仍为假时继续运行,导致数据错乱或逻辑崩溃。
正确做法是把条件检查包在 while 循环中:
synchronized (lock) {
while (!conditionMet()) {
lock.wait();
}
// 此处 conditionMet() 一定为 true
}
-
wait()只能出现在同步块内,且锁对象必须和notify()使用的是同一个 - 每次
wait()返回,都必须重新验证条件——因为唤醒不等于条件成立 - 不要在循环体里写业务逻辑;所有依赖条件的代码必须放在循环外
conditionMet() 必须是可变、线程安全的判断
条件判断不能依赖局部变量或不可变状态。典型错误是用一个普通布尔字段但没加 volatile,或者用非原子操作读取共享状态。
例如,以下写法危险:
// 错误:flag 非 volatile,且读取未同步
private boolean flag = false;
// 线程 A
synchronized (lock) {
while (!flag) { // 可能永远看不到 flag 的变化
lock.wait();
}
}
// 线程 B
flag = true; // 没有同步,A 可能永远卡住
lock.notify();
- 推荐用
volatile字段 + 同步块组合,或直接在同步块内读取共享状态(如queue.isEmpty()) - 如果条件涉及多个字段(比如“缓冲区已满且生产者已暂停”),必须在同步块内一次性读取,避免中间态
- 避免在
conditionMet()中调用可能阻塞或抛异常的方法,否则wait()被打断后逻辑难以恢复
notify 和 notifyAll 的选择直接影响 while 循环是否有效
用 notify() 本身不会导致虚假唤醒,但它可能唤醒错的等待线程——比如两个不同条件共用一个锁,notify() 随机唤醒一个,结果它等的条件根本没变,只能继续 wait。这时 while 循环就是最后一道防线,防止它误执行。
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
常见场景对比:
- 单生产者-单消费者、唯一等待条件 →
notify()足够,但依然要配while - 多个等待条件(如“队列非空”和“队列未满”)、或不确定谁该醒 → 必须用
notifyAll() -
notifyAll()开销略大,但语义更安全;现代 JVM 对无竞争的notifyAll()有优化,不必过度担心性能
别忘了中断处理和 InterruptedException
wait() 可被中断,抛出 InterruptedException。如果忽略它或简单吞掉,会丢失线程中断意图,且可能让 while 循环无限卡住(尤其在条件长期不满足时)。
正确姿势是向上抛、重设中断状态,或在退出前清理:
synchronized (lock) {
while (!conditionMet()) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断标记
throw e; // 或 return / break,视业务而定
}
}
}
- 不要捕获
InterruptedException后什么都不做,这是最常见隐患 - 如果线程被中断时本就该退出等待,可在 catch 块里直接
break或返回,但需确保资源一致 -
wait(long)和wait(long, int)同样会响应中断,超时返回不代表条件成立,仍要走while判断
真正容易被忽略的点是:即使你写了 while,如果条件判断本身不是原子的、或依赖了过期缓存值、或没处理中断,虚假唤醒仍然会穿透进来造成 bug。多线程下,“写对”和“写全”之间差的往往就这一两行。

















