wait()必须放在while循环里,因为可能遭遇虚假唤醒或条件被其他线程修改,if判断一次无法确保条件成立,易导致逻辑错误或数据不一致。

为什么 wait() 必须放在 while 循环里,不能用 if
因为 wait() 可能被虚假唤醒(spurious wakeup),也可能在重新获得锁后发现条件早已被其他线程改回不满足状态。用 if 判断一次就直接往下走,大概率导致逻辑错误或数据不一致。
典型现象:线程从 wait() 返回后,发现队列已空、缓冲区仍满、信号量没真正就绪,却继续执行消费/写入操作,触发 IndexOutOfBoundsException 或死锁。
- 所有基于
wait()/notify()的条件等待,都必须用while重检条件表达式 - 条件变量(如
isEmpty、isFull)必须是对象级别的共享状态,不能是局部计算值 - 检查和 wait 必须在同一个
synchronized块内,否则存在竞态窗口
标准写法长什么样:以生产者-消费者为例
核心不是“怎么写漂亮”,而是“怎么避免漏掉关键环节”。下面这段是经过验证的最小可靠模式:
synchronized (lock) {
while (!conditionMet()) {
lock.wait();
}
// 此时 conditionMet() 一定为 true,且持有锁
doSomething();
}
注意三点:
-
conditionMet()必须是可反复求值的布尔表达式,比如queue.size() > 0,而不是缓存的临时变量wasNotEmpty -
wait()前不能有 return、break 或其他提前退出逻辑,否则条件检查被跳过 - 如果用了带超时的
wait(long timeout),返回后仍要再 check 条件——超时和唤醒都会跳出 wait
notify() 和 notifyAll() 选哪个?
绝大多数情况下该用 notifyAll(),而不是 notify()。这不是性能洁癖,而是避免条件竞争下的永久阻塞。
常见误判场景:多个等待线程监听不同条件(比如“非空”和“非满”),只调 notify() 可能唤醒错的人,而被唤醒者因条件不满足再次 wait,其他真正该醒的线程却永远卡住。
-
notify()仅适用于:等待线程完全同质、条件完全一致、且唤醒任意一个都等价(极少见) -
notifyAll()开销并不比notify()高太多,JVM 会跳过无等待的线程 - 别指望靠“精准唤醒”优化性能——条件复杂时,唤醒后仍要循环检查,本质仍是协作式调度
现代替代方案:为什么还值得学 wait/notify
因为 java.util.concurrent 底层大量使用它,比如 ArrayBlockingQueue 的 take() 和 put()。看不懂 wait/notify,就看不懂这些类的阻塞逻辑、中断响应机制,也容易在自定义同步器中写出唤醒丢失的 bug。
容易被忽略的一点:wait() 会释放锁,但也会在返回前重新获取锁;而 LockSupport.park() 不涉及锁管理——两者语义不同,不能混用或随意替换。
真正在写新代码时,优先用 ReentrantLock.newCondition() 或 BlockingQueue,但只要还在读 JDK 源码、维护老同步逻辑、或面试被问到,while + wait 就绕不开。










