Java wait/notify 机制

wait() / notify() / notifyAll() 是 Java 内置的线程间协作机制,必须在 synchronized 代码块或方法中调用。

核心机制

前提条件

  • 调用 wait() / notify() 的线程必须持有对象的监视器锁(Monitor)
  • 即必须在 synchronized 块/方法中调用
  • 否则抛出 IllegalMonitorStateException

执行流程

线程A 获取锁 → 条件不满足 → wait()释放锁 → 进入 Waiting 状态
线程B 获取锁 → 条件满足 → notify() → 线程B释放锁
线程A 被唤醒 → 重新竞争锁 → 获得锁后继续执行

wait() 后线程什么时候被唤醒?

唤醒方式说明被唤醒时机
notify()随机唤醒一个等待线程无法控制唤醒哪个线程
notifyAll()唤醒所有等待线程所有线程都会被唤醒
interrupt()中断等待线程抛出 InterruptedException
Spurious Wakeup虚假唤醒即使没有 notify,线程也可能被唤醒

虚假唤醒(Spurious Wakeup)

  • 操作系统层面的现象:即使没有 notify 信号,阻塞在 wait() 上的线程也可能被唤醒
  • 解决方案:始终在循环中检查条件
// 错误的写法
if (!condition) {
    wait(); // 被唤醒后不再检查条件
}
 
// 正确的写法 — 始终在循环中 wait
synchronized (lock) {
    while (!condition) {
        lock.wait(); // 唤醒后重新检查条件
    }
    // 执行业务逻辑
}

notify() vs notifyAll()

对比维度notify()notifyAll()
唤醒数量随机一个等待线程所有等待线程
安全性低 — 可能导致信号丢失高 — 所有线程一起竞争
性能好 — 减少上下文切换差 — 多线程竞争锁
推荐使用确信只需要唤醒一个线程不确定时使用(更安全)

信号丢失场景

// 场景:缓冲区空,生产者 put,消费者 take
// 如果使用 notify(),且唤醒的是另一个生产者(而非消费者)
// 消费者继续等待 → 死等(信号丢失)
// 解决方案:使用 notifyAll()

唤醒后需要重新获取 Monitor

  • 是的,线程被唤醒后不能立即从 wait() 返回
  • 线程从 Waiting → Blocked 状态,排队等待获取 monitor 锁
  • 获取到锁后,从 wait() 返回继续执行
  • 如果获取不到锁,线程阻塞在锁竞争上

与 sleep() 的区别

特性wait()sleep()
持有锁时调用释放锁不释放锁
调用条件必须在 synchronized 块中任意地方
唤醒方式notify / notifyAll / 超时 / 中断超时 / 中断
所属类ObjectThread
作用线程间协作(条件等待)暂停当前线程执行

参考链接