前言
在实际的软件开发过程中,经常会碰到:某个模块负责产生数据,然后这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就称为生产者;而处理数据的模块,就称为消费者。另外还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
比如你要寄一封信,大致过程如下:
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、分布式缓存、数据结构等等。