Java中 Object wait 虚假唤醒 Spurious Wakeup 如何用 while 循环解决

作者:袖梨 2026-07-27
Java中wait()必须用while循环检查条件而非if,因存在虚假唤醒(spurious wakeup)——线程可能无通知自行苏醒,导致条件未满足就继续执行;while确保每次唤醒后重新验证条件,只有真正满足时才退出循环。

Java中用wait()时发生虚假唤醒(Spurious Wakeup),不是因为notify()notifyAll()被调用,而是线程在没有明确唤醒信号的情况下自行苏醒。它不违反JVM规范,但会导致条件未满足就继续执行,引发逻辑错误。解决方法不是避免它,而是**始终用while循环检查等待条件是否真正成立**。

为什么不能用 if 判断

if只检查一次,一旦线程被虚假唤醒,就直接往下走,跳过条件重验。比如生产者-消费者场景中,消费者wait()等待队列非空,若被虚假唤醒却没检查队列是否真有元素,就会尝试取空队列,抛出异常或逻辑错乱。

while 循环的正确写法

把等待逻辑包裹在while中,每次唤醒后都重新判断条件。只有条件为true才退出循环,否则继续wait()

✔ 正确示例:

立即学习“Java免费学习笔记(深入)”;

synchronized (lock) {    while (!conditionMet()) {  // 条件不满足就一直等        lock.wait();    }    // 此处 conditionMet() 一定为 true    doSomething();}

✘ 错误写法:

synchronized (lock) {    if (!conditionMet()) {  // 只判断一次,无法应对虚假唤醒        lock.wait();    }    doSomething(); // 可能 conditionMet() 仍为 false!}

conditionMet() 必须是可变、可同步的共享状态

循环判断的条件必须基于多个线程可见且受同一锁保护的变量,例如:

  • 使用volatile修饰的布尔标志(需配合synchronized保证原子性,仅volatile不够)
  • 检查集合大小:queue.isEmpty()(前提是queue的操作都在synchronized(lock)块内)
  • 读取计数器:count < MAXcount由同步代码更新)

关键点:条件检查和wait()必须在同一个synchronized块中,且锁对象一致。

notify 和 notifyAll 的选择影响不大

虚假唤醒与用notify()还是notifyAll()无关,它是底层操作系统或JVM调度导致的。即使只唤醒一个线程,该线程也必须用while重检条件;而notifyAll()唤醒多个线程时,更需要while防止多个线程争抢资源后条件已失效。所以无论哪种唤醒方式,while都是必需的防御手段。

相关文章

精彩推荐