Java版生产者消费者模型,线程间通信
1.这是一个使用 Java 实现的生产者-消费者模型的示例,展示线程间通信。示例1使用BlockingQueue 来简化实现,BlockingQueue底层使用 wait() 和 notify() 机制来协调生产者和消费者线程。
示例1:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumerExample {
public static void main(String[] args) {
// 创建一个容量为10的阻塞队列
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
// 创建生产者线程
Thread producerThread = new Thread(new Producer(queue), "Producer");
// 创建消费者线程
Thread consumerThread = new Thread(new Consumer(queue), "Consumer");
// 启动线程
producerThread.start();
consumerThread.start();
}
}
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " producing: " + i);
queue.put(i); // 放入队列,自动阻塞如果队列满
Thread.sleep(1000); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
Integer item = queue.take(); // 从队列取出,自动阻塞如果队列空
System.out.println(Thread.currentThread().getName() + " consuming: " + item);
Thread.sleep(1500); // 模拟消费耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
说明:
- BlockingQueue:使用
LinkedBlockingQueue作为线程安全的队列,容量为10。put()和take()方法会自动处理线程同步,当队列满时生产者阻塞,当队列空时消费者阻塞。 - 生产者:
Producer线程循环生产1到10的整数,放入队列,每次生产后休眠1秒模拟耗时。 - 消费者:
Consumer线程从队列中取出数据并消费,每次消费后休眠1.5秒模拟耗时。 - 线程通信:
BlockingQueue内部使用wait()和notify()机制实现线程间通信,生产者和消费者通过队列协调工作。 - 异常处理:捕获
InterruptedException并恢复中断状态,确保线程安全退出。
运行结果:
运行程序后,您会看到生产者和消费者交替工作,输出类似:
Producer producing: 1
Consumer consuming: 1
Producer producing: 2
Consumer consuming: 2
...
这个示例展示了生产者-消费者模式的核心思想,适合初学者理解线程间通信。
2. 示例2是使用 wait() 和 notify() 手动实现的生产者-消费者模型,不依赖 BlockingQueue,而是使用自定义的缓冲区和同步机制来实现线程间通信。
示例2:
public class ProducerConsumerManual {
public static void main(String[] args) {
Buffer buffer = new Buffer(5); // 缓冲区容量为5
// 创建生产者线程
Thread producerThread = new Thread(new Producer(buffer), "Producer");
// 创建消费者线程
Thread consumerThread = new Thread(new Consumer(buffer), "Consumer");
// 启动线程
producerThread.start();
consumerThread.start();
}
}
class Buffer {
private int[] items;
private int size;
private int count;
private int in; // 生产者放入位置
private int out; // 消费者取出位置
public Buffer(int size) {
this.items = new int[size];
this.size = size;
this.count = 0;
this.in = 0;
this.out = 0;
}
public synchronized void put(int item) throws InterruptedException {
// 缓冲区满时等待
while (count == size) {
System.out.println(Thread.currentThread().getName() + " waiting: buffer full");
wait();
}
// 放入数据
items[in] = item;
in = (in + 1) % size;
count++;
System.out.println(Thread.currentThread().getName() + " produced: " + item);
// 通知消费者
notify();
}
public synchronized int take() throws InterruptedException {
// 缓冲区空时等待
while (count == 0) {
System.out.println(Thread.currentThread().getName() + " waiting: buffer empty");
wait();
}
// 取出数据
int item = items[out];
out = (out + 1) % size;
count--;
System.out.println(Thread.currentThread().getName() + " consumed: " + item);
// 通知生产者
notify();
return item;
}
}
class Producer implements Runnable {
private final Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
buffer.put(i);
Thread.sleep(1000); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
buffer.take();
Thread.sleep(1500); // 模拟消费耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
说明:
-
Buffer 类:
- 使用固定大小的数组(容量为5)作为缓冲区。
put()和take()方法使用synchronized确保线程安全。- 当缓冲区满(
count == size)时,生产者调用wait()等待;当缓冲区空(count == 0)时,消费者调用wait()等待。 - 生产或消费后,通过
notify()唤醒等待的线程。 - 使用循环队列(
in和out指针)管理缓冲区位置。
-
生产者:
- 生产1到10的整数,放入缓冲区,每次生产后休眠1秒模拟耗时。
-
消费者:
- 从缓冲区取出数据并消费,每次消费后休眠1.5秒模拟耗时。
-
线程通信:
- 使用
wait()和notify()实现线程间通信,生产者和消费者通过缓冲区的状态(满或空)协调工作。 synchronized确保同一时间只有一个线程访问缓冲区。
- 使用
-
异常处理:
- 捕获
InterruptedException并恢复中断状态,确保线程安全退出。
- 捕获
运行结果:
运行程序后,您会看到生产者和消费者交替工作,输出类似:
Producer produced: 1
Consumer consumed: 1
Producer produced: 2
Consumer consumed: 2
Producer produced: 3
...
Producer waiting: buffer full
Consumer consumed: 8
Producer produced: 9
...
关键点:
- 使用
while循环检查条件(而不是if),防止虚假唤醒(spurious wakeup)。 notify()只会唤醒一个等待线程,适合单生产者单消费者场景。如果需要支持多生产者多消费者,可以使用notifyAll()。- 相比
BlockingQueue,手动实现更清晰地展示了wait()和notify()的工作原理。
3. 示例3 是使用 wait() 和 notifyAll() 手动实现的多生产者多消费者模式的 Java 示例。这个示例支持多个生产者和消费者线程,通过同步机制协调访问共享缓冲区。使用 notifyAll() 代替 notify(),以确保在多线程场景下所有等待的线程都能被唤醒检查条件。
实例3:
public class MultiProducerConsumer {
public static void main(String[] args) {
Buffer buffer = new Buffer(5); // 缓冲区容量为5
// 创建3个生产者线程
for (int i = 1; i <= 3; i++) {
new Thread(new Producer(buffer, i), "Producer-" + i).start();
}
// 创建2个消费者线程
for (int i = 1; i <= 2; i++) {
new Thread(new Consumer(buffer, i), "Consumer-" + i).start();
}
}
}
class Buffer {
private int[] items;
private int size;
private int count;
private int in; // 生产者放入位置
private int out; // 消费者取出位置
public Buffer(int size) {
this.items = new int[size];
this.size = size;
this.count = 0;
this.in = 0;
this.out = 0;
}
public synchronized void put(int item, String producerName) throws InterruptedException {
// 缓冲区满时等待
while (count == size) {
System.out.println(producerName + " waiting: buffer full");
wait();
}
// 放入数据
items[in] = item;
in = (in + 1) % size;
count++;
System.out.println(producerName + " produced: " + item);
// 通知所有等待线程
notifyAll();
}
public synchronized int take(String consumerName) throws InterruptedException {
// 缓冲区空时等待
while (count == 0) {
System.out.println(consumerName + " waiting: buffer empty");
wait();
}
// 取出数据
int item = items[out];
out = (out + 1) % size;
count--;
System.out.println(consumerName + " consumed: " + item);
// 通知所有等待线程
notifyAll();
return item;
}
}
class Producer implements Runnable {
private final Buffer buffer;
private final int id;
public Producer(Buffer buffer, int id) {
this.buffer = buffer;
this.id = id;
}
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) { // 每个生产者生产5个数据
int item = i * 100 + id; // 生产唯一数据,如101, 102, ..., 501, 502
buffer.put(item, Thread.currentThread().getName());
Thread.sleep((long) (Math.random() * 1000)); // 随机休眠0-1秒
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final Buffer buffer;
private final int id;
public Consumer(Buffer buffer, int id) {
this.buffer = buffer;
this.id = id;
}
@Override
public void run() {
try {
for (int i = 1; i <= 8; i++) { // 每个消费者消费8个数据
buffer.take(Thread.currentThread().getName());
Thread.sleep((long) (Math.random() * 1500)); // 随机休眠0-1.5秒
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
说明:
-
Buffer 类:
- 使用固定大小的数组(容量为5)作为循环缓冲区。
put()和take()方法使用synchronized确保线程安全。- 当缓冲区满(
count == size)时,生产者调用wait()等待;当缓冲区空(count == 0)时,消费者调用wait()等待。 - 使用
notifyAll()唤醒所有等待线程,适合多生产者多消费者场景,因为notify()可能只唤醒同类型线程(如只唤醒另一个生产者),导致死锁。 - 使用
while循环检查条件,防止虚假唤醒。
-
生产者:
- 创建3个生产者线程,每个生产5个数据(共15个数据)。
- 每个生产者生成唯一的数据(如 Producer-1 生产 101, 201, ..., 501),便于区分。
- 生产后随机休眠0-1秒,模拟不同生产速度。
-
消费者:
- 创建2个消费者线程,每个消费8个数据(共16个数据,略多于生产数据以确保缓冲区最终被清空)。
- 消费后随机休眠0-1.5秒,模拟不同消费速度。
-
线程通信:
- 使用
wait()和notifyAll()实现线程间通信。notifyAll()确保所有等待的生产者和消费者线程都能被唤醒,检查缓冲区状态。 - 同步方法确保同一时间只有一个线程访问缓冲区。
- 使用
-
异常处理:
- 捕获
InterruptedException并恢复中断状态,确保线程安全退出。
- 捕获
运行结果:
运行程序后,您会看到多个生产者和消费者交替工作,输出类似:
Producer-1 produced: 101
Producer-2 produced: 102
Consumer-1 consumed: 101
Producer-3 produced: 103
Consumer-2 consumed: 102
Producer-1 produced: 201
Producer-2 waiting: buffer full
Consumer-1 consumed: 103
Producer-2 produced: 202
...
关键点:
- notifyAll() vs notify():在多线程场景下,
notifyAll()确保所有等待线程(生产者和消费者)都被唤醒,避免只唤醒同类型线程导致的死锁。 - 唯一数据:生产者生成的数据包含线程ID(如101, 102),便于跟踪生产和消费的对应关系。
- 随机休眠:模拟生产者和消费者处理速度的差异,增加并发场景的真实性。
- 缓冲区管理:使用循环队列(
in和out指针)高效管理缓冲区空间。
注意事项:
- 程序设计为生产15个数据,消费16个数据,确保所有数据被消费后,消费者可能因缓冲区空而等待。如果需要程序自动终止,可以在
Buffer类中添加终止条件(如特殊值或标志)。 - 注意这里还可以进一步扩展,添加日志、性能监控,支持动态生产者/消费者数量 等等。
浙公网安备 33010602011771号