1. 什么是生产者消费者模式?
是用于多线程协作
的设计模式,在生产者消费者模型中往往有两类线程:生产者线程、消费者线程,生产者线程通过数据缓冲区进行数据通信,使得消费者线程无需一直傻傻等待生产者线程产生数据
大大提高了通信效率,同时数据缓冲区的存在降低了生产者和消费者直接的耦合度,而且还能缓冲生产速率和消费速率的不平衡。
生产者消费者模式模型图:
模型包含角色:
- 生产者:提交用户请求,将用户请求封装成数据,提交到数据缓冲区。
- 消费者:处理用户请求,将缓冲区中的任务/数据进行处理。
- 数据缓冲区:存放数据/任务的容器(Queue)。
- 任务/数据:生产者向数据缓冲区中提交的数据结构。
- Main:测试生产者消费者模型的启动类。
模型应用场景:
- JDK线程池,ThreadPoolExecutor线程池构造函数的其中一个核心参数就是BlockingQueue,任务队列,线程提交的任务当线程池繁忙的时候就会被放入到任务队列中(相当于数据缓冲区),等待被空闲的线程池线程消费。
- 消息队列:RabbitMQ,Kafka,异步,解耦,削峰,这个就不用多说了。
2. 使用BlockingQueue实现线程安全的生产者消费者模式
关键点:BlockingQueue 提供的 put 和 tack 方法
- put方法,当BlockingQueue未满时放入数据。
- tack方法,当BlockingQueue不为空时获取数据。
// 任务
public class Task {
private int tag; //该变量纯粹用于标记数据
public Task() {}
public Task(int tag) {
this.tag = tag;
}
public int getTag() {
return tag;
}
}
// 生产者
public class Producer implements Runnable{
private BlockingQueue<Task> queue; // 数据缓冲区
private volatile boolean isRunning = true; // 生产者状态
private Producer(){}
public Producer(BlockingQueue<Task> queue){
this.queue = queue;
}
public void stop() {
isRunning = false;
}
@Override
public void run() {
try {
while (isRunning){
Thread.sleep(1000); // 每隔一秒生产一次(让消费者有充足时间去消费)
Task task = new Task(new Random().nextInt(100)); // 生产产品到数据缓冲区中, 随机数标识不同产品
queue.put(task); // 如果共享缓冲区已满就等到有空间了再放入
System.out.println(Thread.currentThread().getName() + " 生产了"+task.getTag());
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
// 消费者
public class Consumer implements Runnable{
private BlockingQueue<Task> queue; // 数据缓冲区
private volatile boolean isRunning = true; // 消费者状态
private Consumer() {}
public Consumer(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void stop() {
isRunning = false;
}
@Override
public void run() {
try {
while (isRunning) {
Task take = queue.take(); // 若缓冲区数据为空就等待有数据在获取
System.out.println("消费了Task"+take.getTag());
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
// 测试启动类
public class Main {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(2);
// 生产者
Producer producer1 = new Producer(queue);
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
// 消费者
Consumer consumer = new Consumer(queue);
// 线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 提交三个生产者到线程池
executorService.execute(producer1);
executorService.execute(producer2);
executorService.execute(producer3);
// 提交一个消费者到线程池执行
executorService.execute(consumer);
// 观察执行结果...
}
}
3. 使用synchronized、wait、notifyAll实现生产者消费者模式
PS:生产动作和消费动作都是相互排斥的,性能较低。
// 产品
public class Task {
private int tag;
public Task(){}
public Task(int tag){
this.tag = tag;
}
public int getTag() {
return tag;
}
}
// 测试启动类
public class Main {
public static void main(String[] args) {
DataBuffer dataBuffer = new DataBuffer();
// 生产者
Producer producer1 = new Producer(dataBuffer);
Producer producer2 = new Producer(dataBuffer);
Producer producer3 = new Producer(dataBuffer);
// 消费者
Consumer consumer = new Consumer(dataBuffer);
// 线程池,执行生产者消费者任务
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(producer1);
executorService.execute(producer2);
executorService.execute(producer3);
executorService.execute(consumer); // 可以将消费者注释掉,以检测生产者是否会超前生产
}
}
// 通用的数据缓冲区接口
public interface GenericDataBuffer {
void put(Task task) throws InterruptedException;
Task tack() throws InterruptedException;
}
// 数据缓冲区实现
public class DataBuffer implements GenericDataBuffer{
private final static int MAX_SIZE = 4; // 缓冲区最大容量
private final AtomicInteger currentSize = new AtomicInteger(0);
private final List<Task> buffer = new LinkedList<>();
// 向缓冲区中提交数据
public synchronized void put(Task task) throws InterruptedException {
// 队列已满,休眠不进行数据提交
while (currentSize.get() >= MAX_SIZE) {
this.wait();
}
buffer.add(task);
currentSize.incrementAndGet();
// 缓冲区中已有数据,唤醒消费者线程进行消费
this.notifyAll();
}
// 获取缓冲区中的数据
public synchronized Task tack() throws InterruptedException {
Task task;
// 队列已空,消费者线程休眠停止消费
while (currentSize.get()<=0) {
this.wait();
}
task = buffer.remove(0);
currentSize.decrementAndGet();
this.notifyAll();
return task;
}
}
// 生产者
public class Producer implements Runnable {
public boolean isRunning = true;
public GenericDataBuffer buffer;
private Producer() {}
public Producer(GenericDataBuffer buffer) {
this.buffer = buffer;
}
public void stop() {
this.isRunning = false;
}
@Override
public void run() {
try {
while (isRunning){
Thread.sleep(1000); // 1 秒生产一次
Task task = new Task(new Random().nextInt(100));
buffer.put(task);
System.out.println(Thread.currentThread().getName()+" 生产了Task "+task.getTag());
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
// 消费者
public class Consumer implements Runnable {
public boolean isRunning = true;
public GenericDataBuffer buffer;
private Consumer(){}
public Consumer(GenericDataBuffer buffer){
this.buffer = buffer;
}
public void stop() {
this.isRunning = false;
}
@Override
public void run() {
try {
while (isRunning) {
Task task = buffer.tack();
System.out.println(Thread.currentThread().getName()+" 消费了Task "+task.getTag());
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
总结
模型中的角色就是那几个:生产者
生产产品
放到数据缓冲区
中,消费者
去缓冲区拿产品自己去消费,构建一个启动类
测试这个模型。
要注意的点就是两个:
- 生产者不能生产的数量达到数据缓冲区的最大值就必须等待。
- 消费者当数据缓冲区没有数据的时候就必须等待。