Java理解生产者-消费者设计模式
在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者;
生产者和消费者之间通过缓冲区(通常是一个阻塞队列)实现通讯, 生产者将产生的数据放入缓冲区,消费者从缓冲区中获取数据。
图片来源网络举个栗子
去食堂吃饭,食堂大叔会先将饭做好,放到食堂窗口,下课了同学会去食堂打饭。
生产者(食堂大叔) -> 生产数据(做饭) -> 缓冲区(食堂窗口) -> 消费数据(打饭) -> 消费者(同学)
生产者消费者实现思路
图片来源网络- 使用Object的 wait()和 notify()方法的实现
public class ProducerConsumerTest {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Producer p1 = new Producer(buffer, 1);
Consumer c1 = new Consumer(buffer, 1);
p1.start();
c1.start();
}
}
/**
* 缓冲区
*/
class Buffer {
private List<Integer> data = new ArrayList<>();
private static final int MAX = 10;
private static final int MIN = 0;
public synchronized int get() {
while (MIN == data.size()) {
try {
wait();
} catch (InterruptedException e) {
}
}
Integer i = data.remove(0);
notifyAll();
return i;
}
public synchronized void put(int value) {
while (MAX == data.size()) {
try {
wait();
} catch (InterruptedException e) {
}
}
data.add(value);
notifyAll();
}
}
class Consumer extends Thread {
private Buffer buffer;
private int number;
public Consumer(Buffer b, int number) {
buffer = b;
this.number = number;
}
public void run() {
int value;
for (int i = 0; i < 10; i++) {
// 从缓冲区中获取数据
value = buffer.get();
try {
// 模拟消费数据
sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("消费者 #" + this.number + " got: " + value);
}
}
}
class Producer extends Thread {
private Buffer buffer;
private int number;
public Producer(Buffer b, int number) {
buffer = b;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
try {
// 模拟生产数据
sleep(500);
} catch (InterruptedException e) {
}
// 将数据放入缓冲区
buffer.put(i);
System.out.println("生产者 #" + this.number + " put: " + i);
}
}
}
- 分析
-
wait()、notify/notifyAll() 必须在
synchronized
代码块执行,可保证当前只有一个线程获取到Buffer对象的锁。所以可以使用线程不安全的ArrayList充当了阻塞队列 -
当消费者获得锁,队列里有数据,消费并尝试唤醒生产者。如果队列里为空,调用 wait(),会挂起当前线程并释放锁,等待被生产者唤醒
-
当生产者获得锁,队列里数据没满,生产并尝试唤醒消费者。队列数据满了,调用wait(),挂起当前线程并释放锁,等待被消费者唤醒
上述使用最基本的Java代码实现生产者消费者模式,实际开发中我们可能会使用BlockingQueue
、ReentrantLock
等等这些更成熟的轮子,但是一通百通吧。感兴趣的可以戳这里 Java实现生产者和消费者的5种方式
为什么要使用生产者消费者模式
顺序执行不就可以了吗?生产者消费者到底有什么意义?
- 并发
生产者直接调用消费者,两者是同步的(阻塞),如果消费者吞吐数据很慢,这时候生产者白白浪费大好时光。而使用这种模式之后,生产者将数据丢到缓冲区,继续生产,完全不依赖消费者,程序执行效率会大大提高。
- 解耦
生产者和消费者之间不直接依赖,通过缓冲区通讯,将两个类之间的耦合度降到最低。