wait/notify 必须在 synchronized 块中调用,且锁对象与调用对象必须一致;应优先使用 notifyAll() 配合 while 条件判断,而非 notify();推荐用 BlockingQueue 或 Condition 替代手写 wait/notify。

wait/notify 必须在 synchronized 块中调用
直接调用 wait() 或 notify() 会抛出 IllegalMonitorStateException,因为这两个方法依赖对象监视器(monitor)——只有持有该对象锁的线程才能操作。很多人误以为“加了锁就行”,但容易忽略锁对象是否和 wait() 调用对象一致。
常见错误写法:
synchronized (lockA) {
obj.wait(); // 错!obj 和 lockA 不是同一个对象
}
正确做法是:锁对象和 wait()/notify() 的调用对象必须是同一个实例。典型协作模式如下:
- 生产者和消费者共用一个共享队列对象
queue - 所有同步块都用
synchronized (queue) -
queue.wait()、queue.notifyAll()全部作用于queue
为什么优先用 notifyAll() 而不是 notify()
notify() 只唤醒一个等待线程,但无法保证唤醒的是需要继续执行的那个——比如多个消费者线程都在等数据,而生产者只放入一条数据后调用 notify(),可能唤醒了一个本该等待更多数据的线程,导致其余线程永久挂起(虚假唤醒之外的逻辑死锁)。
立即学习“Java免费学习笔记(深入)”;
除非你明确知道只有一个线程在等待,且唤醒顺序可控(极少见),否则一律用 notifyAll()。注意它不会引发性能灾难:JVM 会唤醒所有线程,但只有满足条件的那个能抢到锁并继续,其余自动重新进入 WAITING 状态。
关键点:
-
notify()是非确定性的,不适用于通用协作场景 - 即使只期望唤醒一个,也应靠
while循环 + 条件判断来过滤,而不是靠notify()选人 -
notifyAll()配合while (!condition)才是健壮组合
用 BlockingQueue 替代手写 wait/notify
绝大多数业务场景不需要自己实现线程协作逻辑。java.util.concurrent.BlockingQueue(如 ArrayBlockingQueue、LinkedBlockingQueue)已封装好线程安全的阻塞式存取,内部使用 ReentrantLock + Condition 实现,比手写更可靠、更高效。
示例对比:
- 手写:需维护共享变量、同步块、
wait/notify、中断响应、虚假唤醒处理 - 用
BlockingQueue:queue.put(item)自动阻塞直到有空位,queue.take()自动阻塞直到有数据 - 天然支持中断:
put()和take()都声明InterruptedException,可直接响应线程中断
除非你在写底层框架或教学演示,否则别碰 wait/notify。
Condition 接口比 wait/notify 更灵活
当一个锁需要对应多组等待条件(比如“有数据可取”和“有空位可放”),wait/notify 只能共用一个等待队列,容易互相干扰;而 ReentrantLock 的 newCondition() 可创建多个独立 Condition 实例。
例如生产者-消费者中:
-
notFullCondition 用于生产者等待队列未满 -
notEmptyCondition 用于消费者等待队列非空 - 生产者只需
notFull.signal(),不会错误唤醒消费者
这是 wait/notify 根本做不到的粒度控制。不过要注意:Condition.await() 和 signal() 也必须在对应锁的临界区内调用。
真正难的不是写对语法,而是想清楚“谁在等什么条件、什么时候该被唤醒”。这个逻辑错一点,整个协作就卡死或乱序。










