notify()
和 notifyAll()
是 Java 中用于线程间通信的方法,这两个方法都用于唤醒正在等待 (wait()
) 的线程。然而,它们在工作方式和应用场景上有一些重要的区别。
notify() vs notifyAll()
-
notify()
:- 唤醒一个正在等待 (
wait()
) 的线程。 - 如果有多个线程在等待,具体唤醒哪一个线程是不确定的。
- 可能导致死锁,如果唤醒的线程不能使等待条件变为真,而其他线程仍然需要等待。
- 唤醒一个正在等待 (
-
notifyAll()
:- 唤醒所有正在等待 (
wait()
) 的线程。 - 确保所有等待的线程都有机会检查等待条件。
- 唤醒所有正在等待 (
示例
假设有一个资源池,其中有一定数量的资源,线程可以从中获取资源。如果资源池为空,线程需要等待直到有资源可用。
import java.util.LinkedList;
import java.util.Queue;public class ResourcePool {private final Queue<String> pool = new LinkedList<>();private final int capacity;public ResourcePool(int capacity) {this.capacity = capacity;}public synchronized void produce(String resource) throws InterruptedException {while (pool.size() == capacity) {wait();}pool.add(resource);notifyAll(); // 通知所有等待的线程资源可能可用}public synchronized String consume() throws InterruptedException {while (pool.isEmpty()) {wait();}String resource = pool.poll();notifyAll(); // 通知所有等待的线程可能有空间可用return resource;}public static void main(String[] args) {ResourcePool pool = new ResourcePool(2);// Producer threadThread producer = new Thread(() -> {try {for (int i = 1; i <= 5; i++) {pool.produce("Resource " + i);System.out.println("Produced Resource " + i);Thread.sleep(500); // 模拟生产时间}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// Consumer threadThread consumer = new Thread(() -> {try {for (int i = 1; i <= 5; i++) {String resource = pool.consume();System.out.println("Consumed " + resource);Thread.sleep(1000); // 模拟消费时间}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();}
}
说明
-
while
循环与wait()
:- 在调用
wait()
之前,应该使用while
循环来检查等待条件。这是因为wait()
可能会在条件尚未满足时被中断(例如,由notifyAll()
唤醒,但条件仍未满足)。 - 在
produce()
和consume()
方法中,使用while
循环来确保在调用wait()
前后都检查条件。
- 在调用
-
notify()
可能导致死锁:- 如果我们使用
notify()
而不是notifyAll()
,可能会唤醒一个不能使条件变为真的线程,而其他线程仍然需要等待,导致死锁。 - 在上面的例子中,如果我们使用
notify()
,可能会出现生产者线程唤醒另一个生产者线程(而资源池已满),或者消费者线程唤醒另一个消费者线程(而资源池为空)的情况,导致死锁。
- 如果我们使用
-
notifyAll()
确保所有线程都有机会检查条件:- 使用
notifyAll()
可以确保所有等待的线程都被唤醒,并且都有机会检查等待条件是否满足。 - 在上面的代码中,
notifyAll()
确保无论是生产者还是消费者线程,都能在资源池状态改变时被唤醒,并检查条件是否满足。
- 使用
结语
- 使用
notify()
需要非常小心,确保唤醒的线程能够正确处理接下来的事项,否则可能导致死锁。 notifyAll()
更加安全和保守,适用于大多数场景,确保所有等待的线程都有机会检查条件。- 在使用
wait()
时,应配合while
循环使用,以确保在条件不满足时能够重新进入等待状态。