文章来自https://github.com/NieJianJian/AndroidNotes,内容将持续更新,欢迎star。
一. 前言
生产者消费者模式并不是GOF提出的23种设计模式之一,23种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一,它是我们编程过程中最常用的一种设计模式。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
二. 简介
1. 是什么
生产者:用来生产数据的模块。
消费者:用来处理数据的模块。
缓冲区:单单生产者和消费者还构不成生产者-消费者模式,生产者和消费者之间不直接通讯,而是将缓冲区作为中介。生产者将数据放入缓冲区,而消费者将缓冲区的数据取出,进行处理。
生产者消费者模式的大概结构如下图:
为了不至于太抽象,举一个例子:一个饭店有很多厨师和服务员,厨师和服务员是不直接打交道的,而是厨师把菜做好后,放到窗口,服务员从窗口把菜直接端走给客人就好了。在这个过程中:
- 每一个厨师就相当于一个生产者;
- 每一个服务员就相当于一个消费者;
- 放菜的窗口就相当于缓冲区。
2. 解决什么问题
生产者消费者模型问题是线程模型中的经典问题,在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
在线程世界中,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
Q:为什么不让生产者直接调用消费者,而搞个缓冲区?
A:好处如下:
-
解耦:
生产者和消费者都依赖于缓冲区,两者之间不直接依赖,耦合度降低了。
-
支持并发:
由于函数调用是同步的(或者阻塞的),在消费者还在处理,方法没有返回之前,生产者只好一直等待。
当有了缓存区,生产者只需要把生产的数据往缓存去一丢,就可以直接去生产下一个数据了。
-
支持忙闲不均:
当生产者制造数据很快的时候,消费者来不及处理,这时候就可以将数据放入缓冲区,等什么时候消费者空闲了再来取走数据进行处理。
三. 生产者-消费者模型的实现方式
我们来假设:
- 厨师是生产者,服务员是消费者,窗口是缓冲区;
- 由于窗口能放的菜数量是有限的,我们假设只能放5个菜;
- 当厨师做完菜之后需要看一下窗口是不是满了,如果满了,就在旁边抽烟等待;
- 服务员取菜的时候,如果窗口是空的,就在旁边抽烟等待。
先来描述一下类——菜:
public class Food {
public static volatile int counter = 0; //代表生产的第几个菜
public static final int MAX_COUNT = 100; // 假设一天做够100个菜,厨师也就该下班了
public static final int MAX_QUEUE_SIZE = 5; // 窗口最多存放的菜的数量,也就是阻塞队列的大小。
private int i;
public Food() {
i = ++counter;
}
@Override
public String toString() {
return "第" + i + "个菜";
}
}
- 每次创建Food对象,i字段都+1,代表创建的第几道菜。
- MAX_COUNT代表最大菜品数,假设一天做够100到菜就该下班了。
- MAX_QUEUE_SIZE队列最大数,缓冲区队列的最大值,也就是窗口最多存放的菜的数量。
定义一个工具类:
public class SleepUtil {
private static Random random = new Random();
public static void randomSleep() {
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- 让当前线程随机休眠1秒以内的时间,模拟厨师炒菜的耗时和服务员传菜的耗时。
接下来,创建生产者(Cook:厨师类)和消费者(Waiter:服务员类),并且通过不同的方法来实现生产者消费者模式。
3.1 使用wait()和notify()方法实现
public class Cook extends Thread {
private Queue<Food> queue;
public Cook(Queue<Food> queue, String name) {
super(name);
this.queue = queue;
}
@Override
public void run() {
while (true) {
Food food;
SleepUtil.randomSleep(); //模拟厨师炒菜时间
if (Food.counter >= Food.MAX_COUNT) {
System.out.println("今天已经做完 " + Food.counter + " 道菜了,"
+ getName() + "下班!");
return;
} else {
food = new Food();
}
System.out.println(getName() + " 生产了" + food);
synchronized (queue) {
while (queue.size() >= Food.MAX_QUEUE_SIZE) {
try {
System.out.println("队列元素超过5个,为:" + queue.size()
+ " " + getName() + "抽根烟等待中");
queue.wait();
}