引言
在多线程编程中,线程间通信是一个重要且复杂的主题。Java 提供了一套基本的机制来实现线程间通信,即使用 wait()
, notify()
, 和 notifyAll()
方法。这些方法由 Object
类提供,用于协调多个线程对共享资源的访问。本文将详细介绍这些方法的工作原理、使用场景以及一些实际示例。
基本概念
wait()
wait()
方法使当前线程进入等待状态,直到另一个线程调用 notify()
或 notifyAll()
方法唤醒它。调用 wait()
方法时,线程必须持有该对象的监视器锁(即必须在同步块或同步方法内调用 wait()
)。
notify()
notify()
方法唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则其中一个线程将被唤醒,具体哪个线程被唤醒取决于线程调度器的实现。
notifyAll()
notifyAll()
方法唤醒在此对象监视器上等待的所有线程。这些线程将竞争重新获得该对象的监视器锁,并继续执行。
使用场景
生产者-消费者模式
生产者-消费者模式是多线程编程中的经典问题。在这个模式中,生产者线程生成数据并将其放入共享缓冲区,而消费者线程从缓冲区中取出数据进行处理。为了避免缓冲区溢出和空取,生产者和消费者需要协调工作。
示例代码
生产者-消费者实现
以下是一个使用 wait()
和 notify()
实现的简单生产者-消费者示例:
import java.util.LinkedList;
import java.util.Queue;
class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (queue.size() == MAX_SIZE) {
wait();
}
queue.add(value);
System.out.println("Produced: " + value);
value++;
notify();
Thread.sleep(100); // 模拟生产过程
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (queue.isEmpty()) {
wait();
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notify();
Thread.sleep(100); // 模拟消费过程
}
}
}
}
public class Main {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producerThread = new Thread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumerThread = new Thread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producerThread.start();
consumerThread.start();
}
}
代码解释
ProducerConsumer
类中定义了一个共享队列queue
和一个最大容量MAX_SIZE
。produce()
方法生成数据并将其放入队列。当队列已满时,调用wait()
进入等待状态。consume()
方法从队列中取出数据。当队列为空时,调用wait()
进入等待状态。- 当生产者生产了一个数据后,调用
notify()
唤醒等待的消费者。消费者同样在消费了一个数据后调用notify()
唤醒等待的生产者。
注意事项
在同步块或同步方法内使用
wait()
, notify()
, 和 notifyAll()
方法必须在同步块或同步方法内调用,因为它们需要持有对象的监视器锁。如果在非同步块或非同步方法内调用这些方法,将抛出 IllegalMonitorStateException
异常。
避免虚假唤醒
虚假唤醒(spurious wakeups)是指线程在没有收到 notify()
或 notifyAll()
通知的情况下被唤醒。因此,应该总是使用循环来调用 wait()
方法,而不是使用 if
语句:
synchronized (this) {
while (condition) {
wait();
}
// 执行代码
}
使用 notifyAll()
而非 notify()
在某些情况下,使用 notifyAll()
比 notify()
更安全,因为 notifyAll()
可以唤醒所有等待的线程,避免某些线程永远等待的情况。例如,在有多个生产者和消费者时,notifyAll()
更能确保公平性。
结论
通过使用 wait()
, notify()
, 和 notifyAll()
方法,Java 提供了基本的线程间通信机制,可以有效地解决线程间的协作问题。理解并正确使用这些方法,对于编写高效且安全的多线程程序至关重要。
希望本文能帮助你理解 Java 中的线程间通信机制及其应用场景。如果你有任何问题或建议,欢迎留言讨论。