Java 中的 notify() 和 wait():深入理解线程间通信
简介
在多线程编程中,线程之间的有效通信至关重要。Java 提供了 notify()
和 wait()
方法来实现线程间的协调与同步。通过这些方法,线程可以暂停执行、等待特定条件满足,然后由其他线程唤醒继续执行。本文将深入探讨 notify()
和 wait()
的基础概念、使用方法、常见实践以及最佳实践,帮助读者在多线程编程中更好地运用这些机制。
目录
- 基础概念
wait()
方法notify()
方法notifyAll()
方法
- 使用方法
- 调用
wait()
- 调用
notify()
和notifyAll()
- 调用
- 常见实践
- 生产者 - 消费者模式
- 线程间条件等待
- 最佳实践
- 避免死锁
- 合理使用
notify()
和notifyAll()
- 正确处理异常
- 小结
- 参考资料
基础概念
wait()
方法
wait()
是 Object
类的实例方法。当一个线程调用对象的 wait()
方法时,该线程会释放对象的锁,并进入等待状态,直到其他线程调用该对象的 notify()
或 notifyAll()
方法将其唤醒,或者等待时间超时(如果使用了带超时参数的 wait()
方法)。
notify()
方法
notify()
也是 Object
类的实例方法。当一个线程调用对象的 notify()
方法时,它会随机唤醒在此对象监视器(锁)上等待的单个线程。被唤醒的线程将重新竞争获取该对象的锁,获取到锁后继续执行。
notifyAll()
方法
notifyAll()
同样是 Object
类的实例方法。与 notify()
不同,notifyAll()
会唤醒在此对象监视器(锁)上等待的所有线程。这些被唤醒的线程都会竞争获取该对象的锁,最终只有一个线程能获取到锁并继续执行,其他线程将再次进入等待状态。
使用方法
调用 wait()
public class WaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等待线程开始等待...");
lock.wait();
System.out.println("等待线程被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程获取锁并唤醒等待线程...");
lock.notify();
}
});
notifyingThread.start();
}
}
在上述代码中,waitingThread
调用 lock.wait()
进入等待状态并释放 lock
的锁。notifyingThread
在延迟 2 秒后启动,获取 lock
的锁并调用 lock.notify()
唤醒 waitingThread
。
调用 notify()
和 notifyAll()
public class NotifyExample {
public static void main(String[] args) {
Object lock = new Object();
Thread waitingThread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等待线程 1 开始等待...");
lock.wait();
System.out.println("等待线程 1 被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread waitingThread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等待线程 2 开始等待...");
lock.wait();
System.out.println("等待线程 2 被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread1.start();
waitingThread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程获取锁并唤醒所有等待线程...");
lock.notifyAll();
}
});
notifyingThread.start();
}
}
此代码中,waitingThread1
和 waitingThread2
都调用 lock.wait()
进入等待状态。notifyingThread
在延迟 2 秒后启动,调用 lock.notifyAll()
唤醒所有等待线程。
常见实践
生产者 - 消费者模式
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 {
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 {
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()
唤醒对方线程。
线程间条件等待
public class ConditionWaitExample {
private static boolean conditionMet = false;
public static void main(String[] args) {
Object lock = new Object();
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
while (!conditionMet) {
try {
System.out.println("等待线程开始等待条件满足...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待线程条件满足,继续执行...");
}
});
waitingThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
conditionMet = true;
System.out.println("通知线程设置条件为真并唤醒等待线程...");
lock.notify();
}
});
notifyingThread.start();
}
}
在这个例子中,waitingThread
等待 conditionMet
为 true
,在条件不满足时调用 wait()
。notifyingThread
在延迟 2 秒后设置 conditionMet
为 true
并调用 notify()
唤醒 waitingThread
。
最佳实践
避免死锁
死锁是多线程编程中常见的问题,通常由于线程相互等待对方释放资源而导致。为避免死锁,应遵循以下原则:
- 尽量减少锁的持有时间,避免长时间持有锁而不释放。
- 按照固定顺序获取锁,避免交叉获取锁。
- 使用定时锁(如 tryLock()
方法),设置合理的等待时间,避免无限期等待。
合理使用 notify()
和 notifyAll()
- 如果只有一个线程在等待特定条件,使用
notify()
可以提高效率,因为它只唤醒一个线程。 - 如果有多个线程在等待不同条件,或者无法确定哪个线程需要被唤醒,使用
notifyAll()
确保所有等待线程都有机会竞争锁并检查条件。
正确处理异常
在调用 wait()
方法时,需要捕获 InterruptedException
异常。这是因为在等待过程中,线程可能被中断,正确处理异常可以保证程序的健壮性。
小结
notify()
和 wait()
是 Java 多线程编程中实现线程间通信的重要机制。通过合理运用这些方法,可以实现复杂的多线程协调与同步。在实际应用中,需要注意避免死锁、合理选择 notify()
或 notifyAll()
以及正确处理异常,以确保程序的正确性和高效性。
参考资料
- 《Effective Java》 - Joshua Bloch
- 《Java Concurrency in Practice》 - Brian Goetz
希望通过本文的介绍,读者能够深入理解并灵活运用 notify()
和 wait()
方法,提升多线程编程的能力。