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()只唤醒一个,随机选择,可能饥饿
