Java中的wait()、notify()和notifyAll()方法详解
字数 1294 2025-11-06 12:41:12
Java中的wait()、notify()和notifyAll()方法详解
描述
wait()、notify()和notifyAll()是Java中用于线程间协作的底层机制,它们定义在Object类中,必须结合synchronized关键字使用。这些方法通过对象的监视器锁(Monitor)实现线程的等待与唤醒,常用于生产者-消费者模型或条件判断的场景。
1. 方法的基本作用
- wait():当前线程释放锁并进入等待状态,直到其他线程调用同一对象的
notify()或notifyAll()方法,或被中断。 - notify():随机唤醒一个正在等待该对象锁的线程(多个等待线程时唤醒其中一个)。
- notifyAll():唤醒所有正在等待该对象锁的线程,这些线程将竞争锁。
关键点:
- 调用这些方法前,线程必须持有该对象的监视器锁(即必须在
synchronized代码块或方法内)。 - 调用
wait()后,线程会释放锁,允许其他线程获取锁并执行。
2. 底层原理:监视器模型
每个Java对象都有一个关联的监视器锁(Monitor)和一个等待队列(Wait Set)。
- 当线程调用
wait()时,会释放锁并进入等待队列。 - 当其他线程调用
notify()时,JVM从等待队列中移出一个线程到入口队列(Entry Set),使其有机会重新竞争锁。 - 被唤醒的线程需要重新获取锁后才能继续执行。
3. 使用步骤示例(生产者-消费者模型)
以下代码展示一个简单的队列,当队列为空时消费者等待,队列满时生产者等待:
public class WaitNotifyExample {
private final Queue<String> queue = new LinkedList<>();
private final int MAX_SIZE = 5;
public synchronized void produce(String item) throws InterruptedException {
while (queue.size() == MAX_SIZE) {
wait(); // 队列已满,生产者等待
}
queue.add(item);
System.out.println("Produced: " + item);
notifyAll(); // 唤醒所有等待线程(消费者或生产者)
}
public synchronized String consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 队列为空,消费者等待
}
String item = queue.poll();
System.out.println("Consumed: " + item);
notifyAll(); // 唤醒其他线程
return item;
}
}
步骤解析:
- 生产者线程:
- 获取锁后检查队列是否已满,若满则调用
wait()释放锁并等待。 - 生产数据后调用
notifyAll()唤醒可能等待的消费者。
- 获取锁后检查队列是否已满,若满则调用
- 消费者线程:
- 获取锁后检查队列是否为空,若空则调用
wait()释放锁并等待。 - 消费数据后调用
notifyAll()唤醒可能等待的生产者。
- 获取锁后检查队列是否为空,若空则调用
注意:
- 必须用
while而非if检查条件,防止虚假唤醒(Spurious Wakeup)导致状态错误。 - 调用
notifyAll()而非notify()可避免线程饥饿(如只唤醒同类线程)。
4. 常见问题与注意事项
- IllegalMonitorStateException异常:未在同步代码块中调用这些方法时抛出。
- 中断处理:
wait()方法可能被interrupt()中断,需捕获InterruptedException。 - 超时等待:使用
wait(long timeout)避免永久等待。 - 与
Lock和Condition的对比:JUC中的Condition提供了更灵活的等待/唤醒机制(如多条件队列)。
5. 总结
wait()/notify()是Java多线程协作的基石,核心在于:
- 通过对象监视器锁实现线程同步;
- 用
while循环避免虚假唤醒; - 协调线程间状态依赖关系。
掌握这些方法有助于理解更高级的并发工具(如BlockingQueue)的底层实现。