Skip to content

wait / notify

线程间通信

wait()notify() 是 Object 类提供的线程间通信机制。它们必须配合 synchronized 使用,是 Java 并发编程的基础。

为什么需要 wait/notify?

考虑一个场景:生产者生产数据,消费者消费数据。如果消费者在数据还没准备好时就消费,怎么办?

java
// 不用 wait/notify
class BadBuffer {
    private Object data;

    public Object get() {
        // 消费者:数据没准备好,只能空转等待
        while (data == null) {
            // 空转,白耗 CPU
        }
        Object result = data;
        data = null;
        return result;
    }
}

这样 CPU 空转,效率极低。正确做法是用 wait/notify

java
class GoodBuffer {
    private Object data;

    public synchronized Object get() throws InterruptedException {
        while (data == null) {
            wait(); // 数据没准备好,释放锁,等待
        }
        Object result = data;
        data = null;
        notify(); // 通知等待的生产者
        return result;
    }

    public synchronized void put(Object d) throws InterruptedException {
        while (data != null) {
            wait(); // 数据还没被消费,等待
        }
        data = d;
        notify(); // 通知等待的消费者
    }
}

基本方法

方法说明
wait()释放锁,进入等待队列,直到被 notify
wait(long ms)超时等待,ms 毫秒后自动唤醒
notify()唤醒一个等待线程
notifyAll()唤醒所有等待线程

使用规则

  1. 必须在 synchronized 块中调用:否则抛 IllegalMonitorStateException
  2. wait 释放锁:线程进入等待前会释放持有的锁
  3. notify 不立即释放:notify 后线程不会立刻释放锁,需要执行完 synchronized 块

典型模式

模式一:while 循环判断条件

java
synchronized (lock) {
    while (!condition) {  // 用 while,不用 if
        lock.wait();
    }
    // 做事情...
}

用 while 而不是 if 的原因:线程被唤醒后,可能条件仍然不满足(虚假唤醒)。

模式二:notifyAll 优先

java
synchronized (lock) {
    condition = true;
    lock.notifyAll(); // 唤醒所有等待线程
}

为什么优先用 notifyAll?因为 notify 只唤醒一个线程,如果唤醒的是同类(比如多个消费者),可能导致死锁。

实际应用:生产者-消费者

java
class Storage {
    private static final int MAX = 10;
    private Queue<Object> queue = new LinkedList<>();

    public synchronized void produce(Object item) throws InterruptedException {
        while (queue.size() >= MAX) {
            wait(); // 满了,等消费
        }
        queue.add(item);
        System.out.println("生产: " + item);
        notifyAll(); // 通知所有消费者
    }

    public synchronized Object consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 空了,等生产
        }
        Object item = queue.poll();
        System.out.println("消费: " + item);
        notifyAll(); // 通知所有生产者
        return item;
    }
}

虚假唤醒

虚假唤醒是指 wait() 可能无原因地被唤醒。

java
// 不安全的写法
synchronized (lock) {
    if (count == 0) { // ❌ 用 if
        lock.wait();
    }
    // ...
}

// 安全的写法
synchronized (lock) {
    while (count == 0) { // ✅ 用 while
        lock.wait();
    }
    // ...
}

Oracle 官方文档明确说:总是使用 while 循环来检查条件

与 Condition 对比

JDK 5+ 提供了 java.util.concurrent.locks.Condition,功能类似但更灵活:

java
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

lock.lock();
try {
    while (count >= MAX) {
        notFull.await(); // 等待不满
    }
    // 生产...
    notEmpty.signal(); // 唤醒消费者

} finally {
    lock.unlock();
}

Condition 的优势:一个锁可以有多个 Condition,分组更灵活。

总结

wait/notify 的使用口诀:

  1. synchronized 包裹
  2. while 循环判断
  3. notifyAll 优先
  4. 注意虚假唤醒

如果追求更好的并发控制,推荐使用 java.util.concurrent 包下的高级工具:ReentrantLockSemaphoreBlockingQueue 等。

基于 VitePress 构建