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() | 唤醒所有等待线程 |
使用规则
- 必须在 synchronized 块中调用:否则抛
IllegalMonitorStateException - wait 释放锁:线程进入等待前会释放持有的锁
- 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 的使用口诀:
- synchronized 包裹
- while 循环判断
- notifyAll 优先
- 注意虚假唤醒
如果追求更好的并发控制,推荐使用 java.util.concurrent 包下的高级工具:ReentrantLock、Semaphore、BlockingQueue 等。
