Skip to content

wait/notify 机制

线程间的「握手」——一个线程说「我等着」,另一个线程说「好了,轮到你了」。

先看问题

两个线程怎么通信?线程 A 干完了,怎么通知线程 B?

java
// 线程 B:等着
while (!ready) {
    // 轮询?浪费 CPU
}

// 线程 A:干完了
ready = true;

轮询太 low 了。Java 提供了 wait()/notify() 机制。

三个核心方法

方法作用
wait()线程主动让出 CPU,进入等待
notify()唤醒一个等待的线程
notifyAll()唤醒所有等待的线程

关键点wait()释放锁notify() 只是通知,不释放锁。

前提条件

这三个方法必须在 synchronized 块里调用,否则抛 IllegalMonitorStateException

java
// ❌ 错误:在 synchronized 外调用
lock.wait();  // 抛异常

// ✅ 正确:在 synchronized 内调用
synchronized (lock) {
    lock.wait();  // 正常
}

基本用法

java
public class WaitNotifyDemo {

    private static final Object lock = new Object();
    private static boolean ready = false;

    public static void main(String[] args) throws InterruptedException {
        // 线程 B:等着
        Thread waiter = new Thread(() -> {
            synchronized (lock) {
                while (!ready) {
                    try {
                        lock.wait();  // 释放锁,进入等待
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.println("线程 B 收到通知,继续执行");
            }
        }, "Waiter");

        // 线程 A:干完了,通知
        Thread notifier = new Thread(() -> {
            synchronized (lock) {
                ready = true;
                lock.notify();  // 通知 B
            }
        }, "Notifier");

        waiter.start();
        Thread.sleep(100);  // 确保 waiter 先进入等待
        notifier.start();
    }
}

执行流程

线程 B: synchronized(lock) → while(!ready) → lock.wait() [释放锁]
线程 A: synchronized(lock) → ready=true → lock.notify()
线程 B: 被唤醒,重新获取锁 → while 条件不满足 → 继续执行

wait() vs sleep()

最容易搞混的两个方法:

对比wait()sleep()
所属Object 实例方法Thread 静态方法
释放锁✅ 释放❌ 不释放
位置要求必须在 synchronized 内任何地方
唤醒方式notify / 超时超时
java
// sleep 不释放锁
synchronized (lock) {
    Thread.sleep(1000);  // ❌ 睡的时候还拿着锁!
    // 其他线程想进这个 synchronized 块?等吧
}

// wait 释放锁
synchronized (lock) {
    lock.wait();  // ✅ 睡的时候锁放开了
    // 其他线程可以进来
}

为什么用 while 而不是 if?

java
// ❌ 错误:可能「假醒」
synchronized (lock) {
    if (!ready) {
        lock.wait();  // 如果被唤醒,可能条件还是 false
    }
    // 继续执行,假设 ready=true,但可能已经变了
}

// ✅ 正确:醒来后重新检查
synchronized (lock) {
    while (!ready) {
        lock.wait();  // 醒来后重新检查条件
    }
    // 现在可以安全执行了
}

假醒(Spurious Wakeup):操作系统可能不经过 notify 就唤醒线程,必须用 while 重新检查条件。

notify() vs notifyAll()

notify()notifyAll()
唤醒数量随机唤醒一个唤醒所有
风险可能「饥饿」更安全
性能更好差一点
java
// 如果只有一个等待线程,notify 就够了
synchronized (lock) {
    lock.notify();
}

// 如果有多个等待线程,用 notifyAll 更安全
synchronized (lock) {
    lock.notifyAll();
}

经典模式:生产者-消费者

java
public class ProducerConsumer {

    private final Object lock = new Object();
    private Queue<Integer> queue = new LinkedList<>();
    private static final int CAPACITY = 5;

    public void produce(int value) {
        synchronized (lock) {
            while (queue.size() >= CAPACITY) {
                try {
                    lock.wait();  // 队列满了,等
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            queue.offer(value);
            System.out.println("生产: " + value);
            lock.notifyAll();  // 通知消费者
        }
    }

    public int consume() {
        synchronized (lock) {
            while (queue.isEmpty()) {
                try {
                    lock.wait();  // 队列空了,等
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            int value = queue.poll();
            System.out.println("消费: " + value);
            lock.notifyAll();  // 通知生产者
            return value;
        }
    }
}

带超时的 wait()

java
synchronized (lock) {
    while (!condition) {
        lock.wait(5000);  // 等 5 秒,超时自动醒
        // 如果超时,while 循环继续判断条件
    }
    // 执行任务
}

注意事项

1. 先等后通知

java
// ❌ 可能问题:通知在等待之前
notifier.start();
waiter.start();  // notify 已经发过了,waiter 等了个寂寞

// ✅ 正确:确保等待在前
waiter.start();
Thread.sleep(100);  // 确保 waiter 先进入等待
notifier.start();

2. notify 不立即交出锁

java
synchronized (lock) {
    ready = true;
    lock.notify();  // 通知了,但还没释放锁
    // 这段代码还会继续执行
    doSomething();  // 干完才释放锁
}  // ← 这里才释放锁,waiter 才可能被唤醒

3. 中断会抛出异常

java
synchronized (lock) {
    try {
        lock.wait();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();  // 恢复中断状态
    }
}

总结

  • wait()/notify()/notifyAll() 必须用在 synchronized 里
  • wait() 释放锁,sleep() 不释放
  • while 而不是 if 检查条件(防假醒)
  • 多线程等待用 notifyAll() 更安全
  • notify() 只唤醒一个,随机选择,可能饥饿

基于 VitePress 构建