
在多线程同步中,使用wait()时必须配合while循环反复验证条件,而非仅用if判断一次;因为虚假唤醒(spurious wakeup)可能导致线程在未满足业务条件时被唤醒,若不重检条件将破坏线程安全与逻辑正确性。
在多线程同步中,使用wait()时必须配合while循环反复验证条件,而非仅用if判断一次;因为虚假唤醒(spurious wakeup)可能导致线程在未满足业务条件时被唤醒,若不重检条件将破坏线程安全与逻辑正确性。
在Java的Object.wait()机制中,“虚假唤醒”是指线程在没有被显式notify()/notifyAll()唤醒、也未发生中断的情况下,自发地从wait()返回。这是JVM规范允许的行为(源于底层操作系统信号机制的不可控性),并非Bug,而是设计上的必要妥协。
关键在于:wait()的语义是“释放锁并挂起,直到被通知且条件可能成立”,它不保证唤醒时条件一定为真。因此,唤醒后必须重新验证业务条件——这正是while循环的核心作用。
以下对比说明问题:
✅ 正确写法(推荐):
synchronized int get() {
while (!valueSet) { // 条件不满足时持续等待
try {
wait(); // 可能因虚假唤醒、notify或中断返回
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", e);
}
}
System.out.println("Got: " + n);
valueSet = false;
notify(); // 唤醒等待生产的线程
return n;
}
❌ 危险写法(仅用if):
synchronized int get() {
if (!valueSet) { // 仅检查一次!唤醒后不再验证
try {
wait(); // 返回后直接执行后续逻辑,不管valueSet是否真为true
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
// ⚠️ 此处valueSet仍可能为false!但代码已继续执行
System.out.println("Got: " + n); // 可能读取未设置的n(如0或脏值)
valueSet = false;
notify();
return n;
}
⚠️ 后果分析:
- 若发生虚假唤醒,
wait()返回后valueSet仍为false,但if块已执行完毕,线程跳过条件检查,直接消费未就绪的数据; - 可能导致
n为初始值(如0)、数据错乱、IllegalStateException,甚至死锁(例如消费者误认为有数据而通知生产者,生产者却仍在等待); -
notify()可能唤醒错误线程(如多个等待者中非目标线程被唤醒),while可确保其再次进入等待,而if则一去不返。
? 最佳实践总结:
-
永远用
while (condition) wait();,永不单独用if (condition) wait();; - 条件变量(如
valueSet)必须为volatile或受同一锁保护(本例中synchronized已保证可见性); - 使用
notifyAll()通常比notify()更安全(避免信号丢失),尤其在存在多种等待条件时; - Java 5+ 推荐优先使用
java.util.concurrent包中的高级同步工具(如BlockingQueue),它们内部已正确封装了while + wait/notify逻辑。
简言之:while不是“多此一举”,而是对并发不确定性的必要防御——它把条件验证从一次性断言,升级为唤醒后的强制重检,是保障线程协作鲁棒性的基石。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











