在Java中如何实现线程间通信_Javawait与notify机制解析

wait()和notify()必须在synchronized块中调用,且锁对象与等待对象一致;条件检查必须用while而非if以防虚假唤醒;多条件场景应优先使用notifyAll()。

wait() 和 notify() 必须在 synchronized 块中调用

直接调用 wait()notify() 会抛出 IllegalMonitorStateException,因为这两个方法操作的是对象的“内置锁(monitor)”,而只有持有该对象锁的线程才能调用它们。

  • 必须用 synchronized(obj) { ... } 包裹,且 obj 是调用 wait() / notify() 的目标对象
  • 不能用 this.wait() 却在 synchronized(otherObj)

    里——锁对象和等待对象必须一致
  • 推荐显式传入一个专用的锁对象(如 private final Object lock = new Object();),避免与业务同步逻辑冲突

为什么一定要用 while 而不是 if 检查条件

wait() 返回后,被唤醒的线程不保证条件已满足——可能因虚假唤醒(spurious wakeup)或多个线程竞争导致状态再次失效。用 if 只检查一次,极易跳过条件判断直接执行后续逻辑,引发错误。

  • 正确写法是 while (!condition) { obj.wait(); }
  • 每次从 wait() 返回都重新校验条件,确保安全
  • 例如生产者-消费者中,消费者不能只写 if (queue.isEmpty()) wait();,而应写 while (queue.isEmpty()) wait();

notify() 与 notifyAll() 的选择直接影响线程行为

notify() 随机唤醒一个等待线程,notifyAll() 唤醒所有等待线程。二者语义不同,误用会导致死锁或吞吐下降。

  • 当多个线程等待**同一条件但互斥**(如只有一个缓冲区槽位空闲),可用 notify()
  • 当等待线程关注**不同条件**(如既有“非空”又有“非满”等待者),必须用 notifyAll(),否则可能唤醒错的线程,导致无人响应真正就绪的条件
  • 多数场景(尤其涉及多个条件变量时)优先选 notifyAll();性能敏感且逻辑绝对确定时才考虑 notify()
public class BoundedBuffer {
    private final List buffer = new ArrayList<>();
    private final int capacity = 10;
    private final Object lock = new Object();

    public void put(int item) throws InterruptedException {
        synchronized (lock) {
            while (buffer.size() == capacity) {
                lock.wait(); // 等待“有空位”
            }
            buffer.add(item);
            lock.notifyAll(); // 唤醒所有等待者:可能有消费者在等“非空”
        }
    }

    public int take() throws InterruptedException {
        synchronized (lock) {
            while (buffer.isEmpty()) {
                lock.wait(); // 等待“非空”
            }
            int item = buffer.remove(0);
            lock.notifyAll(); // 唤醒所有等待者:可能有生产者在等“未满”
            return item;
        }
    }
}

最常被忽略的一点:即使逻辑看似简单,只要涉及多个条件、多个线程角色或未来可能扩展,就别省略 while 循环和 notifyAll()。JVM 不保证唤醒顺序,也不阻止虚假唤醒——靠机制兜底,而不是靠“我这次应该不会出问题”的假设。