必须用while循环包裹wait()——因为操作系统允许虚假唤醒,if只检查一次条件,唤醒后可能条件仍不成立;while每次唤醒都重新判断,确保条件满足才继续执行。

必须用 while 循环包裹 wait(),不能用 if —— 这是唯一可靠方式。
为什么 if 会导致虚假唤醒出错
操作系统允许线程在没收到 notify() 的情况下被唤醒(合法行为),这叫“虚假唤醒”。如果只用 if (condition) wait();,线程醒来后直接往下执行,但此时条件可能仍不成立,导致逻辑崩溃(比如消费时 number == 0 却继续减一)。
常见错误现象:IllegalMonitorStateException、ArithmeticException: / by zero、输出中出现负数或重复值(如连续两个 “A=>1”)、程序卡死。
-
if只检查一次,醒不来就等,醒来了就信它“真” -
while每次唤醒都重新判断,不满足就继续等 - 即使多个线程同时被
notifyAll()唤醒,也能各自按条件决定是否继续阻塞
synchronized 块里怎么写正确的 while + wait()
所有共享变量读写、条件判断、wait() 和 notifyAll() 必须在同一个 synchronized 块内,否则不是原子操作,会引发竞态。
正确结构是:判断 → 干活 → 通知,且判断必须是 while:
public synchronized void increment() throws InterruptedException {
while (number != 0) { // 注意:是 while,不是 if
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
this.notifyAll(); // 用 notifyAll(),避免唤醒遗漏
}
- 不要把
wait()放在try-catch外层——它必须在同步块内被调用,否则抛IllegalMonitorStateException - 用
notifyAll()而非notify():多个等待线程类型不同(如生产者/消费者)时,notify()可能唤醒错的角色 - 条件变量(如
number)不能是局部变量,必须是实例字段,且不加volatile也 OK —— 因为synchronized保证可见性
什么时候 while 才真正起作用:多线程场景下
两个线程(一产一消)时,if 偶尔也能跑通,但这只是侥幸;一旦加入第三个线程(比如另一个消费者),if 版本大概率立刻出错 —— 因为 notifyAll() 唤醒全部等待者,而只有其中一个能成功修改状态,其余线程必须重新检查条件。
典型使用场景:
- 生产者-消费者模型(>2 个线程参与)
- 资源池(连接池、线程池)的获取/归还逻辑
- 状态机驱动的协作线程(如初始化完成才启动服务)
性能影响极小:每次虚假唤醒只多一次字段读取和分支判断,远小于锁竞争或上下文切换开销。
容易忽略的细节和兼容性陷阱
最容易被跳过的点是:**wait() 前的条件检查,必须和后续修改该条件的代码,在同一把锁保护下**。哪怕你用了 while,但条件变量是在另一个锁里改的,照样出问题。
- 别在
wait()后直接用if再套一层判断 —— 重复嵌套没意义,while已覆盖 - 别把
wait()放在while外面,比如先while (x) { ... }再wait()—— 这等于没包住 - Android API 21+、Java 8–21 都适用这套写法,无版本兼容问题;但 Java 19+ 的虚拟线程(Loom)中仍需遵守,因为
wait()语义未变
真正复杂的地方从来不在语法,而在于:你是否清楚哪个线程在什么时刻修改了那个变量,以及它是否一定经过了同一把锁。










