【JavaEE】生产者消费者模式

作者主页:paper jie_博客

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

本文于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将MySQL基础知识一网打尽,希望可以帮到读者们哦。

其他专栏:《MySQL》《C语言》《javaSE》《数据结构》等

内容分享:本期将会分享设计模式中的生产者消费者模式

目录

什么是阻塞队列

什么是生产者消费者模式

生产者消费者模式的特点

 Java标准库中的阻塞队列

自定义实现一个阻塞队列

普通队列

 实现线程安全

正解

实现阻塞

正解

 基于自己实现的阻塞队列实现一个简单的生产者消费者模型


什么是阻塞队列

阻塞队列它也是队列,遵守着先进先出的原则.且它是一种线程安全的数据结构.它有两个特性:

1. 当队列满的时候,继续入队就会进行堵塞,直到有其他线程从队列中拿走元素才会解除堵塞.

2. 当队列为空的时候,继续出队就会进行堵塞,直到有其他线程从队列中插入元素后才会解除堵塞.

我们的阻塞队列最经典的使用场景就是生产者消费者模式.

举个栗子:

在我们家中,捏饺子一般有两个步骤: 1. 捏饺子皮, 2.包饺子. 假设有三个人.一个滑稽捏饺子皮,其他两个滑稽包饺子. 滑稽捏好饺子皮厚后就会放到板子上,而其他的滑稽就不用直接去捏饺子皮的滑稽手上拿饺子皮,而是去板子上拿即可.这样他们直接就只需要关注这个板子即可. 而这个板子就充当了我们的阻塞队列.

什么是生产者消费者模式

生产者消费者模式就是基于一个中间容器来解决它们之间的强耦合性问题.而这个中间容器就是阻塞队列.加入阻塞队列后,生产者与消费者之间就不会直接联系,而是通过通过阻塞队列来进行数据传输.这样生产者生产数据就不用知道是谁来处理他的数据,不用等待,直接交给阻塞队列即可.而消费者也不会去找生产者要数据,而是去阻塞队列里拿.

生产者消费者模式的特点

1. 通过阻塞队列可以降低生产者与消费者之间的耦合性

假设有三个服务器ABC,BC是将数据处理到,A是接受它们的数据.如果在没有加入阻塞队列的情况下.可能就会发生: 当B或者C挂了,这可能就会导致A也会挂,它们之间是强耦合的关系.因为B或者C的操作中需要涉及到一些关于A的操作.而A的操作也会涉及到一些关于B或C的操作.

但是当加入阻塞队列后就会发生不一样的结果. C,B处理好的结果只需要放到阻塞对列中,而A也只需要去阻塞队列中拿即可.这样B,C的操作对于A的影响就会很小,从而降低了他们之间的耦合性.

 

2. 削峰填谷 

这里就是加入阻塞队列起到一个平衡生产者与消费者之间的处理能力, 加入阻塞队列就可以防止当生产者一下生产出大量数据,而消费者一时间消费不了而导致挂了的问题.

举个栗子:

假设一个场景: 客户端发出请求,服务A接受请求,然后将请求交给BC处理器进行逻辑处理. 在正常情况下处理器是可以及时处理的.但是在一些特殊的时候会有一些突发峰值.外界客户端的请求非常的多.A接受这些请求一下子全部交给B,C服务器来处理,它们一下子可能就会支撑不住. 因为B,C需要就行逻辑处理业务,需要的资源开销就会比较大.如果一下给它大量的请求进行处理,处理器的资源可能就会超过它的上限而导致机器挂了.

但是在加入阻塞队列后就不用担心这种情况了. 就算客户端有大量的请求,A接受后也是传送给阻塞队列,再由B,C去阻塞队列中拿数据处理来慢慢消化.这就算数据再多,只要A服务器,阻塞队列不挂(阻塞队列和A服务器抗压能力很强,它们只需要进行存储数据和传送数据),BC也可以按正常速度进行处理.

 Java标准库中的阻塞队列

在Java标准库中也提供了阻塞队列,如果我们需要使用阻塞队列,只需要使用标准库中的即可. 库中的阻塞队列叫BlockingQueue,它是一个interface接口,它实现的的类有:

ArrayBlockingQueue

LinkedBlockingQueue

priorityBlockingQueue

里面的put和offer方法就是入队方法,但是put是带有阻塞的功能. take也是出队方法,但它也带有阻塞的功能.

public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);queue.put("aaa");System.out.println(queue.take());System.out.println(queue.take());}
}

自定义实现一个阻塞队列

这里我们准备实现一个基于数组的阻塞队列,也就是环形队列.这里队列里面我们需要一个数组,计数器和两个头尾指针,put和take方法. 这里我们分三步来实现这个阻塞队列.

1) 普通队列

2) 实现线程安全

3) 实现阻塞功能

普通队列

class MyarrayBlockingQueue {private int elems[] = null;private int size = 0;private int head = 0;private int tail = 0;public MyarrayBlockingQueue(int capactiy) {elems = new int[capactiy];}public void put(int value) {//判断队列满没满if(size == elems.length) {//阻塞return;}//添加元素elems[tail] = value;tail++;//判断尾指针是不是需要循环到0位置if(tail == elems.length) {tail = 0;}size++;}public int take() {int elem = 0;//判断队列为不为空if(size == 0) {//阻塞return elem;}//出队elem = elems[head];head++;if(head == elems.length) {head = 0;}size--;return elem;}
}

 实现线程安全

实现线程安全,我们就需要加锁.但是当我们下面这个代码这样加锁时,就会出现问题.

public void put(int value) {//判断队列满没满if(size == elems.length) {//阻塞return;}synchronized (this) {//添加元素elems[tail] = value;tail++;//判断尾指针是不是需要循环到0位置if(tail == elems.length) {tail = 0;}size++;}}public int take() {int elem = 0;//判断队列为不为空if(size == 0) {//阻塞return elem;}synchronized(this) {//出队elem = elems[head];head++;if(head == elems.length) {head = 0;}size--;return elem;}}

我们发现如果当有两个线程t1,t2同时使用put或者take方法.假设当前有99个元素,容量为100.当t1执行到if(size == elems.length)后被调度走了,再轮到t2执行.当t2执行完后,队列的元素语已经满了.但是当轮到t1执行时因为上一次的if判断它还会再入队一个元素,这就会出现size为101的问题.

正解

我们将if判断条件也放到锁中就可以了.

public void put(int value) {synchronized (this) {//判断队列满没满if(size == elems.length) {//阻塞return;}//添加元素elems[tail] = value;tail++;//判断尾指针是不是需要循环到0位置if(tail == elems.length) {tail = 0;}size++;}}public int take() {int elem = 0;synchronized(this) {//判断队列为不为空if(size == 0) {//阻塞return elem;}//出队elem = elems[head];head++;if(head == elems.length) {head = 0;}size--;return elem;}}

实现阻塞

这里需要实现阻塞就要使用我们的wait和notify方法.

当队列满时,使用put就会执行wait进入阻塞,只有当使用take调用notify队列才会解除堵塞.

当队列为空时,使用take就会执行wait进入堵塞,只有当使用put调用notify队列才会解除堵塞.

public void put(int value) throws InterruptedException {synchronized (this) {//判断队列满没满if(size == elems.length) {this.wait();return;}//添加元素elems[tail] = value;tail++;//判断尾指针是不是需要循环到0位置if(tail == elems.length) {tail = 0;}size++;this.notify();}}public int take() throws InterruptedException {int elem = 0;synchronized(this) {//判断队列为不为空if(size == 0) {this.wait();return elem;}//出队elem = elems[head];head++;if(head == elems.length) {head = 0;}size--;this.notify();return elem;}}

但是这样又会出现一个问题. 假设有三个线程t1,t2,t3 t1和t2调用put. t3调用take. 且这个队列已经满了. 这里就会有一种情况: t1和t2调用put发现满了就都会在wait那里堵塞,且解锁. 这时t3就执行take方法出队了一个且调用了notify方法唤醒了t1. 则t1也就向下执行入队了一个,此时队列是满了.但是!!!t1的notify方法就可能会唤醒t2.而t2就会直接向下执行又入队一个,但队列是满的,这就出现问题了.

正解

我们可以在if判断那里将if改成while,这样就算被notify唤醒了,也会再次判断队列是不是满/空.才会选择是不是执行还是继续堵塞.

public void put(int value) throws InterruptedException {synchronized (this) {//判断队列满没满while(size == elems.length) {this.wait();return;}//添加元素elems[tail] = value;tail++;//判断尾指针是不是需要循环到0位置if(tail == elems.length) {tail = 0;}size++;this.notify();}}public int take() throws InterruptedException {int elem = 0;synchronized(this) {//判断队列为不为空while(size == 0) {this.wait();return elem;}//出队elem = elems[head];head++;if(head == elems.length) {head = 0;}size--;this.notify();return elem;}}

 基于自己实现的阻塞队列实现一个简单的生产者消费者模型

public class ThreadDemo8 {public static void main(String[] args) {block queue = new block(1000);Thread t1 = new Thread(() -> {int count = 0;while(true) {try {queue.put(count);System.out.println("生产者: " + count);Thread.sleep(1000);count++;} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(() -> {while(true) {try {System.out.println("消费者: " +  queue.take());} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/204380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5: 按钮的点击热区

简介 按钮&#xff0c;尤其是手机端使用的页面按钮&#xff0c;很需要热区&#xff0c;避免用户点击困难。 分析 1.不改变原有的样式 2.扩大可点击范围 具体实现 <template><div class"iconBtnBox"><div:class"props.widthHeight ? iconBt…

期末速成数据库极简版【分支循环函数】(4)

目录 全局变量&局部变量 局部变量定义declare 局部变量赋值select 局部变量赋值select 【1】分支结构IF 【2】分支结构CASE 简单CASE语句 搜索CASE语句 【3】循环结构While 【4】系统函数 常用字符串函数 时间函数 【5】自定义函数—标量函数 函数创建 函…

如何学习Java并发编程

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 2012年我刚转行到互联网…

MySQL三 | 多表查询

目录 多表查询 内连接 隐式内连接 显示内连接 外连接 左外连接 右外连接 自连接 子查询 多表查询 笛卡尔积:集合A和集合B的所有组合情况 A * B 在多表查询时应消除无效的笛卡尔积 内连接 查询的是两张表交集的地方 隐式内连接 SELECT 字段列表 FROM 表1&#xf…

定义一个学生类,其中有3个私有数据成员学号、姓名、成绩,以及若于成员。 函数实现对学生数据的赋值和输出。

#include <stdio.h> // 定义学生类 typedef struct Student { int stuNum; // 学号 char name[20]; // 姓名&#xff0c;假设最长为20个字符 float score; // 成绩 } Student; // 初始化学生信息 void initializeStudent(Student *student, int num, const…

一件小事情

开始我发现表格里面有一个生僻字&#xff0c;开始识别的很好&#xff0c;但是后面就识别的不好&#xff0c;于是我想用之前的方法去识别这个字&#xff0c;但是之前的方法不太好用&#xff0c;因为之前的方法现在不用了&#xff0c;我要用第二种方法&#xff0c;但是第二种方法…

Office Tool Plus 使用教程 让个人也能轻松使用上免费的Office

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

接雨水-困难

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a;输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…

忘记PDF密码了,怎么办?

PDF文件有两种密码&#xff0c;一个打开密码、一个限制编辑密码&#xff0c;因为PDF文件设置了密码&#xff0c;那么打开、编辑PDF文件就会受到限制。忘记了PDF密码该如何解密&#xff1f; PDF和office一样&#xff0c;可以对文件进行加密&#xff0c;但是没有提供恢复密码的功…

在Azure虚拟机中使用XDP Native模式

在进行eBPF的开发中&#xff0c;我们可以看到支持XDP Native的网卡驱动列表&#xff1a; https://docs.cilium.io/en/stable/bpf/progtypes/#xdp 使用azure上的vm进行开发时&#xff0c;我们发现尝试使用XDP Native模式失败&#xff0c; 经过查阅文档&#xff1a; https://www…

优雅草蜻蜓I即时通讯·水银版私有化部署之安卓Android端编译-02

Android 项目配置 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 使用以上Android studio版本 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 下载最低sdk最低版本28 完成后就可以导入项目(项目导入不能开VPN,会导致部分三方库…

lombok原理 @Slf4j 怎么生成get set log

Lombok是一种Java库&#xff0c;通过注解的方式提供了许多有用的功能&#xff0c;包括生成Getter、Setter、日志等。Slf4j注解是Lombok中的一种&#xff0c;它用于自动生成日志记录器&#xff08;Logger&#xff09;。 下面简要介绍一下Lombok的原理&#xff0c;以及Slf4j注解…

一v一聊天

服务端 package 一对一用户;import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Vector;…

使用加密工具The Enigma Protector ,快速保护您的软件安全

我们多次被问到使用Enigma Protector保护软件免遭破解和逆向工程的最佳方法是什么&#xff1f;在这里我将解释保护常用应用程序的技巧是什么。 许多开发人员认为&#xff0c;如果他们只需单击“保护”按钮&#xff0c;保护程序就会自动完成所有操作&#xff0c;无需嵌入额外的…

【Java基础系列】JavaWeb入门

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Linux学习笔记7-SPI的应用和ICM-26068

和IIC类似&#xff0c;我也会针对一个使用SPI通信的芯片ICM-26068来写它的驱动&#xff0c;从而学习SPI通信的应用。 SPI通信的基本原理在单片机中已经详细学习过了&#xff0c;我认为需要理解的是它的全双工&#xff0c;即在同一时间内既可以从主机发送数据到从机&#xff0c;…

「C++」哈希表的实现(unordered系底层)

&#x1f4bb;文章目录 &#x1f4c4;前言哈希表概念哈希函数 哈希冲突闭散列开散列 &#x1f4d3;总结 &#x1f4c4;前言 unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构&#xff0c;使其在查找上的时间复杂度几乎减低到了 O ( 1 ) O(1) O(1)。 哈希…

企业ERP软件定制开发的重点|app小程序网站建设

企业ERP软件定制开发的重点|app小程序网站建设 随着企业信息化程度的不断提高&#xff0c;企业资源计划&#xff08;ERP&#xff09;软件成为了现代企业管理的重要工具。然而&#xff0c;由于不同企业的业务流程、组织结构和管理模式各异&#xff0c;现有的通用ERP软件无法完全…

qt 定时器用法

在qt开发中&#xff0c;定时器是我们经常用到的。我们接下来说一下定时器的三种用法&#xff0c;需要注意的是定时器事件是在主线程中触发的&#xff0c;因此在处理耗时操作时应特别小心&#xff0c;以避免阻塞应用程序的事件循环。 1. 三种定时器使用 1.1 QObject的定时器 …

HTTPS双向认证

HTTPS双向认证和普通的HTTPS认证在安全性和验证方式上有所区别。 安全性&#xff1a; 普通的HTTPS认证&#xff08;单向认证&#xff09;只验证服务器的身份&#xff0c;客户端的身份没有得到验证&#xff0c;因此安全性相对较低。HTTPS双向认证则不仅需要服务器进行身份验证&a…