wait和notify必须在synchronized块中调用,因为它们要求线程持有对象监视器锁以保证状态检查与等待的原子性;需用while循环而非if检查条件,防止虚假唤醒;多线程场景下优先使用notifyAll避免死锁。

wait 和 notify 不能脱离 synchronized 单独使用,否则会抛出 IllegalMonitorStateException;它们不是线程调度工具,而是条件等待机制,必须配合“共享状态 + 循环检查”才能正确协作。
为什么必须在 synchronized 块中调用 wait/notify
Java 的 wait、notify、notifyAll 要求当前线程持有对象的监视器锁(monitor),否则直接抛异常。这不是设计缺陷,而是为了保证“状态检查”和“等待进入”之间的原子性。
常见错误写法:
if (queue.isEmpty()) {
queue.wait(); // 抛出 IllegalMonitorStateException
}
正确做法是:
立即学习“Java免费学习笔记(深入)”;
- 用
synchronized(queue)包裹整个判断 + 等待逻辑 - 检查条件必须用
while,不能用if(防止虚假唤醒) - 唤醒后需重新验证条件是否真正满足,因为
notify不保证谁被唤醒、也不保证条件已就绪
wait/notify 的典型协作模式:生产者-消费者
以阻塞队列为例,核心逻辑围绕一个共享对象(如 ArrayList)和一个状态条件(如 isEmpty() / isFull())展开。
关键点:
- 生产者在
queue.size() == MAX_SIZE时调用queue.wait(),释放锁并等待 - 消费者消费后调用
queue.notify()或更安全的queue.notifyAll() - 消费者同样要在
queue.isEmpty()时wait,生产者插入后notify -
notify()只唤醒一个等待线程,但无法控制唤醒谁;多生产者多消费者场景下,优先用notifyAll()避免死锁
wait(long timeout) 的超时陷阱
wait(1000) 表示最多等 1 秒,但返回原因可能是:超时、被 notify、虚假唤醒。它不改变“仍需循环检查条件”的原则。
错误认知:
- “超时了就说明条件一定不满足” → 错,可能刚超时就有人插入,但你没再检查
- “只调一次 wait 就够了” → 错,必须包裹在
while中,否则可能跳过真实就绪信号 -
wait(0)等价于无时限等待,和wait()相同
notify vs notifyAll:别迷信 notify
很多人以为 notify 更高效,实际在多数协作场景中它是危险的:
- 多个等待线程在不同条件上(如“队列空”和“队列满”),
notify可能唤醒错的线程,导致它再次wait,而真正该醒的却一直挂起 - 即使只有一种等待条件,JVM 不保证唤醒顺序,
notify可能反复唤醒同一个线程,造成饥饿 -
notifyAll开销略大,但逻辑清晰、可预测;现代 JVM 对大量线程等待同一对象的notifyAll做了优化
除非你能 100% 确保:只有一个等待线程,且唤醒后条件必然成立,否则默认用 notifyAll。
最常被忽略的一点:所有基于 wait/notify 的代码,本质上都在模拟一个“条件队列”,而这个条件必须由程序员显式维护——JVM 不知道你在等什么,它只负责挂起和唤醒。漏掉 while 循环、错用 if、忘记同步、乱用 notify,任何一个都会让协作逻辑在高并发下悄无声息地崩坏。










