前言
在理论的软件开发过程中,常常会碰到:某个模块负责产生数据,而后这些数据由另一个模块来负责解决(此处的模块是狭义的,能够是类、函数、线程、过程等)。产生数据的模块,就称为生产者;而解决数据的模块,就称为消费者 。另外还须要有一个 缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
比方你要寄一封信,大抵过程如下:
1、你把信写好——相当于生产者制作数据
2、你把信放入邮筒——相当于生产者把数据放入缓冲区
3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
4、邮递员把信拿去邮局做相应的解决——相当于消费者解决数据
这个缓冲区有什么用?为什么不让生产者间接调用消费者的某个函数,间接把数据传递过来?
长处:
1、解耦
如果让生产者间接调用消费者的某个办法,那么生产者对于消费者就会产生依赖(也就是耦合)。而如果两者都依赖于某个缓冲区,两者之间不间接依赖,耦合也就相应升高了。
2、反对并发
生产者和消费者能够是两个独立的并发主体,生产者把制作进去的数据往缓冲区一丢,就能够再去生产下一个数据。基本上不必依赖消费者的处理速度。如果是生产者间接调用消费者的某个办法的话,因为函数调用是同步的(或者叫阻塞的),在消费者的办法没有返回之前,生产者只能始终等造成节约。事实上,最后生产者和消费者模式的呈现就是为了并发问题。
3、反对忙闲不均
即实用于制作数据的速度时快时慢时。当数据制作快的时候,消费者来不及解决,未解决的数据能够临时存在缓冲区中。等生产者的制作速度慢下来,消费者再缓缓解决掉。
通过 Object 类中的 wait()、notify()、notifyAll()来实现生产者和消费者模式。
利用缓冲区实现:管程法
package com.yang.PC;
// 生产者消费者模型 --> 利用缓冲区解决:管程法
public class Buffer {public static void main(String[] args) {Syncontainer container = new Syncontainer();
new Productor(container).start();
new Consumer(container).start();}
}
// 生产者
class Productor extends Thread{
Syncontainer container;
public Productor(Syncontainer container){this.container = container;}
// 生产
@Override
public void run() {for (int i = 0; i < 10; i++) {System.out.println("生产了"+i+"鸡");
container.push(new Chicken(i));
}
}
}
// 消费者
class Consumer extends Thread{
Syncontainer container;
public Consumer(Syncontainer container){this.container = container;}
// 生产
@Override
public void run() {for (int i = 0; i < 10; i++) {System.out.println("生产了第"+container.pop().id+"鸡");
}
}
}
// 缓冲区
class Syncontainer{
// 定义容器大小
Chicken[] chickens = new Chicken[10];
int count = 0; // 容器计数器
// 生产者将产品放入容器
public synchronized void push(Chicken chicken){
// 如果容器满了,就须要期待消费者生产
if (count ==chickens.length){
try {this.wait(); // 告诉并期待消费者生产
} catch (InterruptedException e) {e.printStackTrace();
}
}
// 如果没有满,则将产品丢入容器
chickens[count] = chicken;
count++;
// 装满了,告诉消费者生产
this.notifyAll();}
// 消费者生产产品
public synchronized Chicken pop(){
// 判断容器中是否有鸡
if (count == 0){
try {this.wait(); // 期待生产者生产
} catch (InterruptedException e) {e.printStackTrace();
}
}
// 如果容器中有鸡
count--;
Chicken chicken = chickens[count];
// 吃完了,告诉生产者生产
this.notifyAll();
return chicken;
}
}
// 产品
class Chicken{
int id; // 产品 id
public Chicken(int id){this.id = id;}
}
利用标记位实现:信号灯法
package com.yang.PC;
public class Flag {public static void main(String[] args) {TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();}
}
// 演员 --> 生产者
class Player extends Thread{
TV tv;
public Player(TV tv){this.tv = tv;}
@Override
public void run() {for (int i = 0; i < 10; i++) {if (i%2 == 0){this.tv.play("胸口碎大石");
}else {this.tv.play("举重");
}
}
}
}
// 观众 --> 消费者
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){this.tv = tv;}
@Override
public void run() {for (int i = 0; i < 10; i++) {tv.watch();
}
}
}
// 产品 --> 节目
class TV{
// 演员表演,观众期待 T
// 观众观看,演员期待 F
String show; // 节目
boolean flag = true;
// 表演
public synchronized void play(String show){if (!flag){
try {this.wait(); // 演员期待
} catch (InterruptedException e) {e.printStackTrace();
}
}
System.out.println("演员表演了"+show);
// 告诉观众观看
this.notifyAll();
this.show = show;
this.flag = !this.flag;
}
// 观看
public synchronized void watch() {if (flag){
try {this.wait(); // 期待观看
} catch (InterruptedException e) {e.printStackTrace();
}
}
System.out.println("观看了"+show);
// 告诉演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
总结:
生产者和消费者模式是通过一个容器 (缓冲区) 来解决生产者和消费者的强耦合关系。生产者即产生数据的模块,消费者是解决数据的模块。位于它们之间的缓冲区能够使得生产者生成数据后无需期待消费者索取,消费者也无需间接向生产者索要数据。这样做最大的益处就是能够实现解耦。此外还能够反对并发和忙闲不均。
最初
欢送关注公众号:前程有光,支付一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!这些材料的内容都是面试时面试官必问的知识点,篇章包含了很多知识点,其中包含了有基础知识、Java 汇合、JVM、多线程并发、spring 原理、微服务、Netty 与 RPC、Kafka、日记、设计模式、Java 算法、数据库、Zookeeper、分布式缓存、数据结构等等。