wait和notify必须在synchronized块中调用,否则抛illegalmonitorstateexception;需用while循环检查条件防虚假唤醒;优先用notifyall()避免唤醒错误线程;超时等待和中断也须正确处理。

wait 和 notify 必须在 synchronized 块里调用
这是最常踩的坑:直接调用 wait() 或 notify() 会抛出 IllegalMonitorStateException。JVM 要求当前线程必须持有对象的监视器锁(即已进入该对象的 synchronized 代码块或方法),才能调用这两个方法。
正确写法是:
synchronized (obj) {
obj.wait(); // 或 obj.notify()
}
常见错误包括:
- 在非 synchronized 方法里调用
wait() - 锁对象和调用
wait()的对象不一致(比如synchronized(this)却对另一个other.wait()) - 用
ReentrantLock替代 synchronized 后,误以为能混用wait()
为什么推荐用 notifyAll() 而不是 notify()
notify() 只唤醒一个等待线程,但无法控制唤醒哪一个;而多个线程可能在等不同条件(比如生产者/消费者共用同一把锁、但等待“有空位”或“有数据”两种状态)。如果只用 notify(),可能唤醒错类型的线程,导致它醒来后条件仍不满足,又立刻回去 wait,造成死锁或饥饿。
立即学习“Java免费学习笔记(深入)”;
所以除非你 100% 确认只有一个线程在等、且它的唤醒条件必然成立,否则一律用:
synchronized (queue) {
queue.notifyAll();
}
注意:notifyAll() 不等于性能差——现代 JVM 对无竞争场景做了优化,真正影响性能的是锁竞争本身,不是唤醒数量。
wait() 要放在 while 循环里,不能用 if
虚假唤醒(spurious wakeup)是真实存在的:即使没人调用 notify(),wait() 也可能提前返回。JVM 规范允许这种行为,Linux 的 futex 实现、某些信号处理机制都可能导致它。
因此必须用 while 检查条件是否真正满足:
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
return queue.poll();
}
用 if 的后果是:线程被唤醒后直接取数据,结果 poll() 返回 null 或抛 NoSuchElementException。
其他易错点:
- 条件判断逻辑和
notify()唤醒逻辑必须基于同一把锁、同一个对象 - 修改共享状态(如
queue.add())和notifyAll()必须在同一个synchronized块中,否则存在竞态窗口
wait(long timeout) 的超时判断容易被忽略
wait(1000) 返回有两种可能:被 notify() 唤醒,或超时自动返回。很多代码只检查是否被唤醒,却没重判条件,导致逻辑错乱。
正确姿势仍是 while 循环 + 条件重检:
synchronized (flag) {
while (!flag.get()) {
flag.wait(1000);
// 不需要额外 if 判断“是否超时”,因为循环会再次检查 flag
}
}
特别注意:wait(long) 不保证精确唤醒,系统调度延迟、GC 暂停都会让实际等待时间更长;它只是“至少等这么久”,不是“正好等这么久”。若需严格定时,应配合 System.nanoTime() 手动计算剩余等待时间并重入 wait()。
另外,wait() 被中断时抛 InterruptedException,必须处理——不能简单吞掉,通常应恢复中断状态:Thread.currentThread().interrupt()。










