java中安全使用wait/notify需守住三关:必须在synchronized块内用同一锁对象调用;条件判断必须用while循环校验,防虚假唤醒;notify/notifyall选择须匹配等待语义,且状态修改与通知须原子化。

Java中在synchronized块里安全使用wait和notify,核心是守住三道关:锁必须持有、条件必须循环校验、唤醒必须配对且语义匹配。跳过任一环节都容易引发死锁、空指针、虚假唤醒或线程饥饿。
必须在synchronized块内调用,且锁对象要一致
wait()和notify()不是普通方法,它们操作的是JVM层面的对象监视器(monitor)。调用前线程必须已获得该对象的锁,否则直接抛IllegalMonitorStateException。
- 正确写法:用同一个对象做synchronized目标,也用它调用wait/notify
- 错误写法:synchronized(obj1) { obj2.wait(); } —— 锁对象和调用对象不一致
- 推荐做法:声明private final Object lock = new Object();,避免this锁被外部干扰
条件判断必须用while,不能用if
线程可能被“虚假唤醒”(spurious wakeup)——没被notify就从wait返回。仅靠if判断会导致后续逻辑基于错误前提执行,比如从空队列取元素。
- 错误示例:if (queue.isEmpty()) { lock.wait(); } → 醒来后不检查,直接poll()会出错
- 正确写法:while (queue.isEmpty()) { lock.wait(); } → 每次醒来都重检条件
- 条件变量(如queue、ready)必须是volatile或由同一把锁保护,否则可能读到旧值
notify和notifyAll的选择要看等待者是否等价
notify()只唤醒一个等待线程,但无法指定唤醒谁;notifyAll()唤醒全部,由JVM调度竞争锁。选错会埋下隐患。
- 适合notify()的场景:单生产者-单消费者,或多个消费者等待同一类资源(如连接池取连接),唤醒任意一个即可
- 适合notifyAll()的场景:多个线程等待不同条件(如有的等“数据就绪”,有的等“配置加载完成”),或存在状态广播需求(如关闭信号)
- 注意:notify()后若被唤醒线程发现条件仍不满足,它会继续wait()——这是正常行为,不是bug
生产与消费逻辑必须在锁内完成关键操作
修改共享状态和发出通知必须原子化。如果先改状态再出锁、再notify,中间就有竞态窗口:其他线程可能抢先进入并破坏状态。
- 正确顺序:synchronized(lock) { 修改条件(如queue.add(item)); lock.notify(); }
- 不要拆开:synchronized(lock) { queue.add(item); } lock.notify(); ❌
- 唤醒后,被唤醒线程需重新获取锁才能继续,所以业务逻辑(如消费)应放在wait退出后的synchronized块内
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











