深入理解 Java 中的 wait() 和 notify()
简介
在多线程编程的世界里,线程之间的协调与通信至关重要。Java 提供了 wait()
和 notify()
方法,它们是对象级别的机制,用于线程间的交互。通过这两个方法,线程能够暂停执行并等待特定条件满足,然后由其他线程唤醒。这篇博客将详细探讨 wait()
和 notify()
的概念、使用方法、常见实践以及最佳实践,帮助你在多线程编程中更好地运用它们。
目录
- 基础概念
wait()
方法notify()
方法notifyAll()
方法
- 使用方法
- 调用
wait()
- 调用
notify()
和notifyAll()
- 代码示例
- 调用
- 常见实践
- 生产者 - 消费者模型
- 线程间的条件等待
- 最佳实践
- 避免死锁
- 合理使用
notify()
和notifyAll()
- 使用
wait()
的超时机制
- 小结
- 参考资料
基础概念
wait()
方法
wait()
是 Object
类的实例方法。当一个线程调用对象的 wait()
方法时,该线程会释放对象的锁,并进入等待状态。直到其他线程调用同一个对象的 notify()
或 notifyAll()
方法将其唤醒,或者等待时间超时(如果使用了带超时参数的 wait()
方法)。
notify()
方法
notify()
也是 Object
类的实例方法。当一个线程调用对象的 notify()
方法时,它会唤醒在此对象监视器(锁)上等待的单个线程。如果有多个线程在等待,会随机选择一个被唤醒。被唤醒的线程将竞争对象的锁,获取锁后继续执行。
notifyAll()
方法
notifyAll()
同样是 Object
类的实例方法。它会唤醒在此对象监视器(锁)上等待的所有线程。所有被唤醒的线程都会竞争对象的锁,获取锁后继续执行。
使用方法
调用 wait()
调用 wait()
方法需要在同步块或同步方法中进行,因为调用 wait()
方法时,线程需要释放对象的锁。示例代码如下:
public class WaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程开始等待");
lock.wait();
System.out.println("线程被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("主线程唤醒等待线程");
lock.notify();
}
}
}
调用 notify()
和 notifyAll()
notify()
和 notifyAll()
方法也需要在同步块或同步方法中调用,因为它们需要获取对象的锁才能操作等待队列中的线程。在上述代码中,主线程通过 lock.notify()
唤醒了等待的线程。如果使用 notifyAll()
,则会唤醒所有在 lock
上等待的线程。
代码示例
下面是一个更完整的示例,展示生产者 - 消费者模型中 wait()
和 notify()
的使用:
import java.util.LinkedList;
import java.util.Queue;
class Producer implements Runnable {
private final Queue<Integer> queue;
private final int capacity;
public Producer(Queue<Integer> queue, int capacity) {
this.queue = queue;
this.capacity = capacity;
}
@Override
public void run() {
int value = 0;
while (true) {
synchronized (queue) {
while (queue.size() == capacity) {
try {
System.out.println("队列已满,生产者等待");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(value++);
System.out.println("生产: " + value);
queue.notify();
}
}
}
}
class Consumer implements Runnable {
private final Queue<Integer> queue;
public Consumer(Queue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println("队列已空,消费者等待");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("消费: " + value);
queue.notify();
}
}
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
int capacity = 5;
Thread producerThread = new Thread(new Producer(queue, capacity));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
常见实践
生产者 - 消费者模型
在生产者 - 消费者模型中,生产者线程生产数据并放入队列,消费者线程从队列中取出数据进行处理。当队列已满时,生产者线程调用 wait()
方法等待;当队列已空时,消费者线程调用 wait()
方法等待。当有新数据放入队列或从队列取出数据时,调用 notify()
或 notifyAll()
方法唤醒等待的线程。
线程间的条件等待
在多线程环境中,线程可能需要等待某个条件满足后才能继续执行。例如,一个线程可能需要等待另一个线程完成某项任务后才能继续。这时可以使用 wait()
和 notify()
方法来实现线程间的条件等待。
最佳实践
避免死锁
死锁是多线程编程中常见的问题。为了避免死锁,要确保所有线程以相同的顺序获取锁,并且在适当的时候释放锁。在使用 wait()
和 notify()
时,要注意唤醒的线程是否能够获取到所需的锁继续执行。
合理使用 notify()
和 notifyAll()
notify()
适用于只需要唤醒一个等待线程的场景,这样可以减少竞争,提高性能。而 notifyAll()
适用于需要唤醒所有等待线程的场景,例如当某个条件发生变化,所有等待线程都需要重新检查条件时。
使用 wait()
的超时机制
wait()
有一个带超时参数的重载方法 wait(long timeout)
。使用这个方法可以避免线程无限期等待,提高程序的健壮性。例如:
synchronized (lock) {
try {
lock.wait(5000); // 等待 5 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
小结
wait()
和 notify()
是 Java 多线程编程中强大的工具,用于线程间的通信和协调。通过理解它们的基础概念、正确的使用方法、常见实践以及最佳实践,你可以编写出高效、健壮的多线程程序。在实际应用中,要根据具体的需求合理使用这些方法,避免出现死锁等问题。
参考资料
- Oracle Java 文档 - Object 类
- 《Effective Java》第 2 版,Joshua Bloch 著
- 《Java 并发编程实战》,Brian Goetz 等著
希望这篇博客能帮助你深入理解并高效使用 Java 中的 wait()
和 notify()
方法。如果你有任何问题或建议,欢迎在评论区留言。