虚假唤醒是线程未被notify()或notifyall()显式唤醒却从wait()返回的现象,需用while循环重检守卫条件,conditionmet须volatile或受同一锁保护,优先使用notifyall()而非notify()。

虚假唤醒到底是什么现象
虚假唤醒是指线程在没有被 notify() 或 notifyAll() 显式唤醒的情况下,从 wait() 返回。JVM 规范允许这种行为,尤其在 Linux 的 futex 实现或信号中断、多核调度竞争下容易触发。表现就是:线程“自己醒了”,但守卫条件(guard condition)依然不成立,继续执行会出错。
必须用 while 而不是 if 包裹 wait()
这是最核心的规范动作。用 if 会导致虚假唤醒后直接跳过条件检查,进入临界区;而 while 强制每次唤醒后重新验证条件是否真正满足。
正确写法示例:
synchronized (lock) {
while (!conditionMet) {
lock.wait();
}
// 此时 conditionMet 一定为 true
doSomething();
}
-
conditionMet必须是 volatile 或受同一锁保护的共享变量 - 不要在
wait()前加日志或耗时操作,否则可能破坏原子性 - 永远不要假设
wait()返回就意味着条件已就绪
notify() 和 notifyAll() 怎么选
只用 notify() 的前提是:所有等待线程守卫的是**完全相同的条件**,且唤醒一个就足够推进全局状态。现实中几乎不存在这种场景。
更安全的做法是统一用 notifyAll(),尤其当:
- 多个不同条件共用同一个锁(如生产者/消费者共用
queue锁) - 存在嵌套等待逻辑(如超时等待 + 条件等待混合)
- 使用了
ReentrantLock+Condition,但多个Condition实例仍可能因实现细节产生干扰
notify() 的性能优势在绝大多数业务场景里微乎其微,却极易引发隐蔽的线程饥饿或死锁。
wait() 前后必须同步且锁一致
wait() 只能在已持有对象监视器(即已进入 synchronized 块)的前提下调用,否则抛 IllegalMonitorStateException。常见错误包括:
- 在 synchronized 外调用
wait() - 锁对象和
wait()调用对象不一致(比如 synchronized(lockA) 但 lockB.wait()) - 用
this.wait()却在外部 synchronized(otherObj)
还有一点容易忽略:wait() 返回后,线程重新获得锁的过程是阻塞的,此时若其他线程长时间持有该锁,你看到的“假唤醒”可能其实是锁竞争延迟导致的误判。
最麻烦的不是语法错误,而是条件变量未被正确建模——比如把非原子的复合判断(如 list.size() > 0 && list.get(0) != null)直接塞进 while,中间可能被并发修改。这时候光靠 while 没用,得重构状态封装或改用 java.util.concurrent 工具类。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










