生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦、消息队列等场景。
在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时生产者的生产速度特别快,但消费者的消费速度跟不上,俗称“产能过剩”,又或是多个生产者对应多个消费者时,大家可能会手忙脚乱。如何才能让大家更好地配合呢?这时在生产者和消费者之间就需要一个中介来进行调度,于是便诞生了生产者消费者模式。
总体大纲
BlockingQueue 方式
思路
核心就是通过一个阻塞队列来缓冲。
生产者将生产的产品放入阻塞队列中即可,如果阻塞队列满了,则等阻塞队列有空位了再将产品放入。
消费者从阻塞队列中消费产品,如果阻塞队列为空,则等阻塞队列有产品了再消费。
代码实现
public static void blockingQueue() {
// 阻塞队列,容量为 10
BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
// 生产任务
Runnable producer = () -> {
while (true) {
try {
// 生产,放入到阻塞队列中去
blockingQueue.put(new Object());
System.out.println(Thread.currentThread().getName() + " 进行生产");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动两个生产者线程进行生产
new Thread(producer, "producer1").start();
new Thread(producer, "producer2").start();
// 消费任务
Runnable consumer = () -> {
while (true) {
try {
// 消费,从阻塞队列中拿
blockingQueue.take();
System.out.println(Thread.currentThread().getName() + " 进行消费");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动两个消费者线程进行消费
new Thread(consumer, "consumer1").start();
new Thread(consumer, "consumer2").start();
}
阻塞队列三对获取和存储元素
API的区别:
take 和 put 是阻塞的获取和存储元素的方法。
poll 和 offer 是不阻塞的获取元素和存储元素的方法。,并且 poll 和 offer 可以指定超时时间。
add 和 remove 存取元素,队列满时 add 抛异常,队列空时 remove 抛异常。
Condition 方式
思路
其实就是自己通过 Condition 模拟一个阻塞队列的实现,流程和上面一样。
代码实现
public class MyBlockingQueueForCondition {
/**
* 队列
*/
private Queue queue;
/**
* 队列最大长度
*/
private int max = 16;
private ReentrantLock lock = new ReentrantLock();
/**
* 队列没有空
*/
private Condition notEmpty = lock.newCondition();
/**
* 队列没有满
*/
private Condition notFull = lock.newCondition();
public MyBlockingQueueForCondition(int size) {
this.max = size;
queue = new LinkedList();
}
/**
* 向队列中添加元素
*/
public void put(Object o) throws InterruptedException {
lock.lock();
try {
// 用 while 不用 if 的原因是防止虚假唤醒
while (queue.size() == max) {
// 阻塞生产者线程
notFull.await();
}
queue.add(o);
// 唤醒消费者线程
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
/**
* 向队列中获取元素
*/
public Object take() throws InterruptedException {
lock.lock();
try {
while (queue.size() == 0) {
// 阻塞消费者线程
notEmpty.await();
}
Object item = queue.remove();
// 唤醒生产者线程
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
wait / notifyAll 方式
思路
其实就是自己通过 wait / notifyAll 模拟一个阻塞队列的实现,流程和上面一样。
代码实现
class MyBlockingQueue {
/**
* 队列长度
*/
private int maxSize;
private LinkedList<Object> storage;
public MyBlockingQueue(int size) {
this.maxSize = size;
storage = new LinkedList<>();
}
/**
* 向队列中添加元素
*/
public synchronized void put() throws InterruptedException {
// 用 while 不用 if 的原因是防止虚假唤醒
while (storage.size() == maxSize) {
wait();
}
storage.add(new Object());
notifyAll();
}
/**
* 向队列中获取元素
*/
public synchronized void take() throws InterruptedException {
while (storage.size() == 0) {
wait();
}
System.out.println(storage.remove());
notifyAll();
}
}