什么是生产者消费者模型?
举个例子:以DOTA来说,辅助进行拉野,大哥收野,辅助就是生产者,大哥就是消费者,辅助拉一波,大哥清一波.这两个配合起来,就是生产者与消费者
但是这样效率很低,毕竟一波野怪还不够让大哥挪动的,所以我们对其进行优化,加入队列的思想.
生产者消费者模型队列:
以一个饭店为例,厨师做一个菜,服务员传一个菜,但是在这个时候出现了问题,如果生产速度过快,消费能力过弱,或者生产能力过弱,消费能力过强,有一方就需要等待,会造成资源的浪费.
下边就要引入生产者与消费者模型的队列,上边的例子,厨师就是生产者,服务员就是消费者,但是两者不直接进行通讯,而是通过一个缓冲区,生产者对数据进行生产之后将其放入缓冲区,而消费者直接从缓冲区取数据,两者并不直接进行通讯.
而这个缓冲区,就通过阻塞队列来实现, 队列的特性就是先进先出,所以不会出现后生产的数据被先拿走,或者先生产的数据没有被使用的情况.
下面通过一个例子来了解一下简单的生产者消费者模型.
这里我们使用的队列为BlockingQueue,线程安全,也就是同一时刻,只允许一个线程对其进行访问,不会同时出现多个线程访问的情况,它将会阻塞线程.
先上代码:
实体类:
package producerConsumer;
//测试用实体类
public class Food {
private String name;//食物名称
private double price;//食物价格
public Food(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
消费者
public class ConsumerRunner implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(foodBlockingQueue.take().toString());//从队列中拿取元素
TimeUnit.MILLISECONDS.sleep(200);
}
} catch (InterruptedException e) {
System.out.println("interrupted");
}
System.out.println("Ending of ConsumerRunner");
}
}
生产者:
public class ProducerRunner implements Runnable {
//生产数据并且向队列添加
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Food food = new Food("鸡肉", 50.25);//新建food实体
System.out.println("生产成功");
foodBlockingQueue.put(food);//填入队列
TimeUnit.MILLISECONDS.sleep(200);
}
}catch(InterruptedException e){
System.out.println("interrupted");
}
System.out.println("Ending of ProducerRunner");
}
}
测试用类:
//测试用类
public class pcTest {
public static BlockingQueue<Food> foodBlockingQueue = new LinkedBlockingQueue<Food>() ;//静态队列方便全局调用
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();//创建线程池
exec.execute(new ProducerRunner());//启动生产者线程
exec.execute(new ConsumerRunner());//启动消费者线程
TimeUnit.MILLISECONDS.sleep(2000);//延缓main()线程时间,体现效果
exec.shutdownNow();//终止exec旗下所有线程.
}
}
结果:
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
生产成功
Food{name='鸡肉', price=50.25}
interrupted
interrupted
Ending of ProducerRunner
Ending of ConsumerRunner
下面说一个稍微复杂的情况,当你既是消费者,又是生产者的时候,并且需要通过你消费的数据从而加工生成新的数据,举个例子比如菜农是生产者,饭店是消费者, 而饭店又是菜品的生产者,下级的消费者是服务员(顾客点菜传菜用),这个时候单向的队列就不能满足需求了.
下面看一个经典的吐司blockingQueue案例
实体类吐司:
public class Toast {
public enum Status{DRY,BUTTERED,JAMMED}//面包状态,白面包,抹了黄油的,抹了果酱的
private Status status= Status.DRY;//默认是白面包
private final int id;
public Toast(int id){
this.id=id;//吐司ID
}
//抹黄油!
public void butter(){
this.status=Status.BUTTERED;
}
//抹果酱!
public void jammed(){
this.status=Status.JAMMED;
}
public Status getStatus() {
return status;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "Toast{" +
"status=" + status +
", id=" + id +
'}';
}
}
做吐司的:
public class Toaster implements Runnable {
private ToastQueue toastQueue;//一个吐司队列
private int count=0;//计数器
private Random random =new Random(47);//随机
public Toaster(ToastQueue tq){
toastQueue = tq;//通过有参构造传入队列
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
TimeUnit.MILLISECONDS.sleep(100 + random.nextInt(500));//做吐司
Toast t = new Toast(count++);//从0开始 每生产一个编号+1 把编号传入当做吐司ID
System.out.println(t.toString());
toastQueue.put(t);
}
}catch(InterruptedException e){
System.out.println("线程中断");
}
System.out.println("吐司制作结束,没面粉了!");
}
}
给白面包抹黄油的:
//黄油怪,涂抹黄油
public class Butterer implements Runnable {
private ToastQueue dryQueue,butteredQueue;//建立白面包队列和黄油面包队列
//这里解释一下,因为这里抹黄油的既是白吐司的消费者,也黄油面包的生产者,况且,黄油面包还可以被别人消费,所以这里要声明两个队列
public Butterer(ToastQueue dryQueue,ToastQueue butteredQueue) {
this.dryQueue =dryQueue;
this.butteredQueue = butteredQueue;
}
@Override
public void run() {
try{
while(!Thread.currentThread().isInterrupted()){
Toast toast = dryQueue.take();//拿白面包
toast.butter();//抹黄油
TimeUnit.MILLISECONDS.sleep(200);//模拟抹黄油
System.out.println(toast.toString());//打印状态
butteredQueue.put(toast);//将黄油吐司放入队列.
}
}catch(InterruptedException e){
System.out.println("黄油中断");
}
System.out.println("抹黄油结束");
}
}
给黄油吐司抹果酱的:
public class janmmer implements Runnable {
private ToastQueue butteredQueue,finishQueue;//黄油面包,抹果酱结束.
public janmmer(ToastQueue butteredQueue, ToastQueue finishQueue) {
this.butteredQueue = butteredQueue;
this.finishQueue = finishQueue;
}
@Override
public void run() {
try{
while(!Thread.currentThread().isInterrupted()){
Toast t = butteredQueue.take();//拿黄油吐司
t.jammed();//抹果酱
TimeUnit.MILLISECONDS.sleep(200);
System.out.println(t.toString());//打印状态
finishQueue.put(t);//扔进结束队列
}
}catch(InterruptedException e){
System.out.print("抹果酱中断");
}
System.out.println("抹果酱程序结束");
}
}
吃吐司的人:
public class Eater implements Runnable {
private ToastQueue finishQueue;
private int counter=0;
public Eater(ToastQueue finishQueue) {
this.finishQueue = finishQueue;
}
@Override
public void run() {
try{
while(!Thread.currentThread().isInterrupted()){
Toast t =finishQueue.take();//拿面包
if(t.getId()!=counter++||t.getStatus()!=Toast.Status.JAMMED){
//如果面包编号不对(顺序上错了),或者面包的没有涂果酱,会报错
System.out.println("错误: 这不是我要的面包!,他不是果酱面包!");
}else{
System.out.println("真,真香"+t.toString());
}
}
} catch(InterruptedException e) {
System.out.println("中断,不吃了");
}
System.out.println("用餐结束");
}
}
吐司队列,方面书写,语义明确:
public class ToastQueue extends LinkedBlockingQueue {}
测试类:
public class ToastTest {
public static void main(String[] args) throws InterruptedException {
ToastQueue dryToast =new ToastQueue();
ToastQueue butteredToast = new ToastQueue();
ToastQueue finToast=new ToastQueue();
//创建三个吐司队列
ExecutorService exec = Executors.newCachedThreadPool();//创建线程池
exec.execute(new Toaster(dryToast));//做吐司
exec.execute(new Butterer(dryToast,butteredToast));//抹黄油
exec.execute(new janmmer(butteredToast,finToast));//抹果酱
exec.execute(new Eater(finToast));//吃
TimeUnit.MILLISECONDS.sleep(2000);
exec.shutdownNow();
}
}
测试结果:
Toast{status=DRY, id=0}
Toast{status=DRY, id=1}
Toast{status=BUTTERED, id=0}
Toast{status=JAMMED, id=0}
真,真香Toast{status=JAMMED, id=0}
Toast{status=BUTTERED, id=1}
Toast{status=DRY, id=2}
Toast{status=JAMMED, id=1}
真,真香Toast{status=JAMMED, id=1}
Toast{status=BUTTERED, id=2}
Toast{status=JAMMED, id=2}
真,真香Toast{status=JAMMED, id=2}
Toast{status=DRY, id=3}
Toast{status=BUTTERED, id=3}
Toast{status=JAMMED, id=3}
真,真香Toast{status=JAMMED, id=3}
Toast{status=DRY, id=4}
中断,不吃了
用餐结束
黄油中断
抹果酱中断抹果酱程序结束
线程中断
吐司制作结束,没面粉了!
抹黄油结束
由于BlockingQueue是阻塞队列,并且由于队列的特性,我们可以看出食客是按照顺序吃的,这样不需要线程同步,中间每个对象传递都是唯一顺序,从队列中取也只能按照队列的顺序来.
这个例子准确来说不是特别严谨,体会其中生产者与消费者的关系,其中也没有对中断后的数据进行后续清理操作,主要表达消费者同时是生产者时,通过队列来交互信息的思想.