wait/notify 必须在对应对象的 synchronized 块内调用,否则抛 IllegalMonitorStateException;Condition 的 await/signal 必须与 ReentrantLock 绑定使用,且需用 while 循环防范虚假唤醒并正确处理中断。

wait/notify 必须在 synchronized 块里调用
直接调用 wait() 或 notify() 会抛 IllegalMonitorStateException,因为这两个方法依赖当前线程持有对象的 monitor 锁。不是“加了锁就行”,而是必须在该对象的同步块内调用——比如对 obj 调用 obj.wait(),就得写在 synchronized(obj) { ... } 里。
- 常见错误:在
synchronized(this)块里调用了另一个对象的wait(),锁对象不匹配,照样报错 - 别用
Thread.sleep()模拟等待——它不释放锁,也不参与通信逻辑 -
wait()会释放锁并挂起线程;notify()不释放锁,只是唤醒一个等待线程,被唤醒的线程要重新竞争锁才能继续执行
Condition 的 await/signal 要和 ReentrantLock 绑定使用
Condition 不是独立存在的,它必须由 ReentrantLock 实例通过 newCondition() 创建。脱离锁实例直接 new Condition() 会编译失败;单独调用 await() 而没先 lock.lock(),运行时抛 IllegalMonitorStateException。
- 一个
ReentrantLock可以创建多个Condition,适合不同等待条件(比如“缓冲区非空”和“缓冲区非满”各用一个) -
signal()唤醒的是同个Condition上等待的线程,不会误唤醒其他条件的线程,比notify()更精准 - 务必在
try-finally中释放锁:lock.lock(); try { condition.await(); } finally { lock.unlock(); }
虚假唤醒(spurious wakeup)必须用 while 循环检查条件
无论 wait() 还是 await(),都可能在没有被显式唤醒、也没有超时的情况下返回。JVM 规范允许这种行为,Linux futex、Windows WaitForSingleObject 都有类似机制。所以永远不要用 if 判断条件后直接 wait,否则可能跳过检查,导致逻辑错乱。
- 正确写法是
while (!conditionMet) { obj.wait(); }或while (!conditionMet) { condition.await(); } - 典型场景:生产者-消费者中,消费者不能只判断“队列为空”就 wait,得循环检查“队列是否仍为空”
- 即使只有一个生产者一个消费者,也不能省掉 while——虚假唤醒和条件竞争是两个独立问题
notify() 和 signal() 不保证唤醒顺序,别依赖 FIFO
notify() 随机选一个等待线程唤醒;signal() 默认也是非公平唤醒(除非 ReentrantLock 构造时传 true 开启公平模式)。这意味着:唤醒顺序 ≠ 等待顺序,更不等于你期望的业务顺序。
立即学习“Java免费学习笔记(深入)”;
- 如果业务强依赖唤醒顺序(比如任务优先级),得自己维护等待队列,用
signalAll()+ 条件过滤,或改用PriorityBlockingQueue等更高层结构 -
notifyAll()/signalAll()开销更大,但能避免因漏唤醒导致的死锁——尤其在多个条件共用同一把锁时,推荐优先考虑 - 别在循环里反复
notify():一次唤醒就够了,多调用没额外效果,还可能干扰调度
最易被忽略的一点:wait() 和 await() 都可能被中断,必须处理 InterruptedException。不响应中断不仅违反协作原则,还会让线程卡死在等待状态,且无法被 Thread.interrupt() 正常终止。










