初始JavaEE篇——多线程(5):生产者-消费者模型、阻塞队列
找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
文章目录
- 阻塞队列
- 生产者—消费者模型
- 生产者—消费者模型的优势:
- 生产者—消费者模型的劣势:
- Java标准库中的阻塞队列:
- 模拟实现阻塞队列:
前面我们学习多线程的经典案例之一:饿汉模式与懒汉模式。两者的区别是创建类的实例的时机不同,前者是迫不及待的去创建类的实例,而后者是迫不得已去创建类的实例。这样就导致了前者在 get 方法中只有"读"操作,不会造成线程安全问题,而后者会出现线程安全问题。最后经过我们的不断深入探索并解决了其中的问题。首先是进行加锁操作,避免了修改操作原子性,其次是加了 if 判断语句,避免了不必要的加锁,从而导致的性能下降,最后,针对指令重排序的问题,在引用变量中加上了 volatile 关键字。如果想更加深入了解,可以点击下面的链接:饿汉模式、懒汉模式、指令重排序等
阻塞队列
现在我们来学习另外一个经典的案例:阻塞队列。
阻塞队列是属于队列的一种,但是和普通的队列相比,它具有以下的特性:
1、它具备线程安全的特点,即使在多线程的环境下,也是可以正常使用的。
2、阻塞特性:1)当队列为空时,如果再去队列中取元素的话,会发生阻塞,直至队列不为
空;2)当队列满了时,如果再去队列中插入元素的话,也会发生阻塞,直至队列不为满。
生产者—消费者模型
阻塞队列的主要应用场景是:“生产者—消费者模型”。那什么是生产者,什么又是消费者呢?简单理解就是,生产者与消费者之间是通过某种资源进行来进行交互的。生产者,就是生产这个资源的,而消费者,就是消耗这个资源的。
例如,在我们日常中,最常见的就是包饺子,包饺子需要擀面皮的人、包饺子的人、放面皮的布。(肉已经被绞肉机给搞好了) 这里就是一个经典的"生产者一消费者模型"。
生产者:擀面皮的人、消费者:包饺子的人、阻塞队列:放面皮的布。这里生产者与消费者进行交互的就是"面皮"这种资源。
我们在日常生活中,有两种包饺子的方式:
1、家里面几个人全部一起参与包饺子的全过程。即每个人都需要 擀面皮、包饺子。而擀面杖只有一个,那么当一个人在进行擀面皮时,另外几个人都得阻塞等待,当这个人把面皮给擀完之后,才会释放,这样下一个人才有机会去使用。这个擀面杖就是我们前面学习的锁。
上面的方式,我们会发现一个很大的缺陷:当其中一个人在生产面皮时,其余的人得阻塞等待,也就是有空闲时间。这对于计算机来说,简直就是浪费,因此下面这种方式更为合理。
2、一个人专门擀面皮,另外的人负责包饺子,这样就不会导致生产者或者消费者会出现空闲的情况(生产速度与消费速度是一致的)。
生产者—消费者模型的优势:
1、解耦合:
生产者与消费者避免了直接交互,而是通过阻塞队列来进行交互,这样有利于代码的解耦合,使得后期的维护成本变低。
2、削峰填谷:
当 生产者—消费者模型 应用于两个服务器时,就可以达到削峰填谷的效果。
因此为了避免上述的情况,我们需要对用一个阻塞队列来处理这种"突然的大量请求的情况"。例如,学校选课的时候,通常就会出现这样的情况。
解决方法:使用一个阻塞队列来充当缓冲的作用。当A服务器突然接收到有大量的请求时,这个阻塞队列便会接收这些请求,但是还是以平常的速度给到B服务器,这样B服务器还是会正常运行,这就是 “削峰”。当这个峰值过去之后,就是平常少量的请求,而阻塞队列这时候就会来处理在高峰期接收的请求,这样B服务器还是以平常的速度在处理请求,这就是"填谷"。阻塞队列通过降低高峰期的发送请求,而是在低谷期来处理,这样B服务器就是以一个平均的速度在处理请求。
注意:
1、阻塞队列通常可以接收并存放很多的请求。
2、高峰期比较短,所以阻塞队列一般不会出现满的情况。
生产者—消费者模型的劣势:
1、引入阻塞队列之后,整体的结构相交以前更为复杂了,同时也需要更多的机器进行部署,使生产环境的结构更复杂,同时管理起来也更为麻烦了。
2、效率也有一定的影响。之前是A服务器和B服务器直接进行交互,现在多了个阻塞队列,消息的传递所消耗的时间也变多了。
Java标准库中的阻塞队列:
Java的标准库中提供的阻塞队列是:BlockingQueue。
我们在日常的开发中,主要就是使用:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。下面是使用的示例:
public class Test {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100);// 在多线程中,队列的插入方法要用put。因为其带有阻塞功能,且线程安全queue.put(1);queue.put(2);queue.put(3);queue.put(4);// 同样多线程中的删除也要用takeint n = queue.size();for (int i = 0; i < n; i++) {System.out.print(queue.take()+" "); // 1 2 3 4}}
}
我们现在可以去看一下阻塞功能。
public class Test {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);// 在队列为空的情况下,尝试去取元素System.out.println(queue.take()); // 由于是单线程,因此会一直阻塞,即死等。queue.put(1);// 在队列为满足,尝试去插入新元素queue.put(2);}
}
同样下面去尝试插入新元素时,也是会发生阻塞等待的,也是死等的情况。
public class Test {public static void main(String[] args) {int n = 1;System.out.println("队列的总容量:"+n);BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(n);System.out.println("队列此时的容量:"+queue.size());Thread t1 = new Thread(()-> {try {System.out.println("阻塞队列为空,尝试取出元素,等待其他的线程插入元素");queue.take();System.out.println("成功取出元素~");} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(()->{try {for (int i = 0; i < 3; i++) {Thread.sleep(1000); // 确保t1线程先执行到take方法,并放慢让我们观察System.out.println("正在尝试插入第" + (i + 1) + "个元素");queue.put(i);System.out.println("插入成功~");}} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();t2.start();}
}
运行结果:
模拟实现阻塞队列:
要求:我们主要是实现队列的 put方法、take方法、size方法即可。
思路:put、take方法都需要保证线程安全和阻塞的特性。
线程安全,我们直接对代码进行加锁操作即可;
阻塞特性:当队列为满时,要阻塞到其他线程使用掉其中的对头元素,即得等待其他线程调用take方法来唤醒当前因队列满而造成的阻塞,这也就需要用到我们前面学习的wait 和 notify 方法。
阻塞队列代码:
public class MyBlockingQueue {// 基于数组去模拟实现private static int[] array = null;private static int usedSize = 0; // 元素个数private int head = 0; // 头指针private int tail = 0; // 尾指针public MyBlockingQueue() {array = new int[10];}public MyBlockingQueue(int capacity) {if (capacity < 0) {throw new RuntimeException();} else if (capacity >= Integer.MAX_VALUE) {array = new int[Integer.MAX_VALUE];} else {array = new int[capacity];}}// put方法public void put(int x) throws InterruptedException {synchronized (this) { // 保证线程安全while (usedSize >= array.length) { // 满足阻塞队列的特性// 阻塞等待this.wait(); // 等待其他线程取出元素,使队列不为满}array[tail] = x;tail = (tail+1) % array.length; // 这里是采用循环队列的方式usedSize++;this.notify(); // 唤醒空的阻塞}}public int take() throws InterruptedException {synchronized (this) { // 保证线程安全while (usedSize <= 0) { // 满足阻塞队列的特性// 阻塞等待this.wait(); // 等待其他线程插入元素,使队列不为空}int ans = array[head];head = (head+1) % array.length;usedSize--;this.notify(); // 唤醒满的阻塞return ans;}}public int size() {return usedSize;}
}
测试代码:
public class Test {public static void main(String[] args) throws InterruptedException {int n = 1;MyBlockingQueue queue = new MyBlockingQueue(n);System.out.println("队列的总容量为:"+n);System.out.println("队列当前的容量为:"+queue.size());Thread t1 = new Thread(()->{try {Thread.sleep(500);System.out.println("队列为空,阻塞等待别的线程插入数据");queue.take();System.out.println("取出元素~");} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(()->{for (int i = 0; i < 3; i++) {try {Thread.sleep(1000);System.out.println("正在尝试插入第"+(i+1)+"个元素");queue.put(i);System.out.println("插入成功~");} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}
运行结果:
注意:
1、我们使用加锁操作,是为了避免出现下面这种情况:一个线程在修改,另一个线程在读取,把数据修改之后,可能会造成另一个线程执行有误,因此我们得对代码进行加锁操作,是同一时刻只能有一个线程去进行修改操作(读取操作是不会影响数据的),因此对于修改操作的代码,都得处于 synchronized 代码块中,而上述 put、take 方法的大部分代码都是修改操作,因此我们就将整个代码逻辑都置于 synchronized 代码块中了。
2、put、take 方法中之所以将判断阻塞的条件放到 while 循环中,是因为可能会出现下面这样的情况:有三个线程都是处于put方法的阻塞状态,而这时新来了一个执行take方法的线程,其会随机唤醒三个线程中的一个,当三个线程中,某个线程执行完 notify 方法之后,也会随机唤醒剩下的两个线程,但是此时这个唤醒操作不符合要求,因为我们是希望将处于take方法的阻塞线程所唤醒,因此这个是错误唤醒,所以我们要用 while 循环去再次线程判断到底是不是因为正常唤醒而被唤醒的。
好啦!本期 初始JavaEE篇——多线程(4):生产者-消费者模型、阻塞队列 的学习之旅 就到此结束啦!
相关文章:
初始JavaEE篇——多线程(5):生产者-消费者模型、阻塞队列
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏:JavaEE 文章目录 阻塞队列生产者—消费者模型生产者—消费者模型的优势:生产者—消费者模型的劣势: Java标准库中的阻…...
2024年下教师资格证面试报名详细流程❗
⏰ 重要时间节点: (一)下半年笔试成绩查询:11月8日10:00 (二)注册报名:11月8日10:00-11日18:00 (三)网上审核:11月8日10:00-11日18:00 (四&#x…...
软考:常用协议和端口号
常用协议及其对应的端口号如下: TCP/IP协议: TCP(传输控制协议):端口号为6UDP(用户数据报协议):端口号为17 网络应用协议: HTTP(超文本传输协议)…...
Linux更改符号链接
目录 1. 删除旧链接 2. 创建新的符号链接 例如我的电脑上有两个版本的cuda,11.8和12.4 1. 删除旧链接 rm cuda 2. 创建新的符号链接 ln -s /usr/local/cuda-11.8/ /usr/local/cuda...
int main(int argc,char* argv[])详解
#include <stdio.h> //argc 是指命令行输入参数的个数; //argv[]存储了所有的命令行参数, //arg[0]通常指向程序中的可执行文件的文件名。在有些版本的编译器中还包括程序文件所在的路径。 //如:"d:\Production\Software\VC_2005_Test\Win32控制台应用程序\Vc_T…...
单片机原理及应用笔记:C51流程控制语句与项目实践
作者介绍 周瑞康,男,银川科技学院,计算机人工智能学院,2022级计算机科学与技术8班本科生,单片机原理及应用课程第八组。 指导老师:王兴泽 电子邮箱2082545622qq.com 前言: 本篇文章是参考《…...
大数据日志处理框架ELK方案
介绍应用场景大数据ELK日志框架安装部署 一,介绍 大数据日志处理框架ELK(Elasticsearch、Logstash、Kibana)是一套完整的日志集中处理方案,以下是对其的详细介绍: 一、Elasticsearch(ES) 基本…...
VQGAN(2021-06:Taming Transformers for High-Resolution Image Synthesis)
论文:Taming Transformers for High-Resolution Image Synthesis 1. 背景介绍 2022年中旬,以扩散模型为核心的图像生成模型将AI绘画带入了大众的视野。实际上,在更早的一年之前,就有了一个能根据文字生成高清图片的模型——VQGAN…...
docker中使用ros2humble的rviz2不显示问题
这里写目录标题 docker中使用ros2humble的rviz2不显示问题删除 Docker 镜像和容器删除 Docker 容器Linux服务器下查看系统CPU个数、核心数、(make编译最大的)线程数总结: RVIZ2 不能显示数据集 docker中使用ros2humble的rviz2不显示问题 问题描述: roo…...
【AIGC】2024-arXiv-Lumiere:视频生成的时空扩散模型
2024-arXiv-Lumiere: A Space-Time Diffusion Model for Video Generation Lumiere:视频生成的时空扩散模型摘要1. 引言2. 相关工作3. Lumiere3.1 时空 U-Net (STUnet)3.2 空间超分辨率的多重扩散 4. 应用4.1 风格化生成4.2 条件生成 5. 评估和比较5.1 定性评估5.2 …...
正则表达式:文本处理的强大工具
正则表达式是一种强大的文本处理工具,它允许我们通过定义一系列的规则来匹配、搜索、替换或分割文本。在编程、文本编辑、数据分析和许多其他领域中,正则表达式都扮演着重要的角色。本文将介绍正则表达式的基本概念、语法和一些实际应用。 正则表达式的…...
Doris单机安装
1、安装包下载 官网地址:https://doris.apache.org/zh-CN/docs/gettingStarted/quick-start/ 下载地址:https://apache-doris-releases.oss-accelerate.aliyuncs.com/apache-doris-3.0.2-bin-x64.tar.gz 2、操作系统环境准备 #环境准备 cat /proc/cp…...
ubuntu内核更新导致显卡驱动掉的解决办法
方法1,DKMS指定内核版本 用第一个就行 1,借鉴别人博客解决方法 2,借鉴别人博客解决方法 方法2,删除多于内核的方法 系统版本:ubuntu20.24 这个方法是下下策,如果重装驱动还是不行,就删内核在…...
【Java数据结构】树】
【Java数据结构】树 一、树型结构1.1 概念1.2 特点1.3 树的类型1.4 树的遍历方式1.5 树的表示形式1.5.1 双亲表示法1.5.2 孩子表示法1.5.3 孩子双亲表示法1.5.4 孩子兄弟表示法 二、树型概念(重点) 此篇博客希望对你有所帮助(帮助你了解树&am…...
Java面试题——微服务篇
1.微服务的拆分原则/怎么样才算一个有效拆分 单一职责原则:每个微服务应该具有单一的责任。这意味着每个服务只关注于完成一项功能,并且该功能应该是独立且完整的。最小化通信:尽量减少服务之间的通信,服务间通信越少,…...
Python 中 print 函数输出多行并且选择对齐方式
代码 # 定义各类别的标签和对应数量 categories ["class0", "class1", "class2", "class3", "class4", "class5"] counts [4953, 547, 5121, 8989, 6077, 4002]# 设置统一的列宽 column_width 10# 生成对齐后…...
书生营L0G3000 Git 基础知识
任务1: 破冰活动:自我介绍 用vi就行了 按照教程来就好了 git会报错密码,输入的时候换成token就好了 https://stackoverflow.com/questions/68775869/message-support-for-password-authentication-was-removed 提交。(github上预览自己的…...
【C++初阶】模版入门看这一篇就够了
文章目录 1. 泛型编程2. 函数模板2. 1 函数模板概念2. 2 函数模板格式2. 3 函数模板的原理2. 4 函数模板的实例化2. 5 模板参数的匹配原则2. 6 补充:使用调试功能观察函数调用 3. 类模板3 .1 类模板的定义格式3. 2 类模板的实例化 1. 泛型编程 在C语言中࿰…...
Spring Bean创建流程
Spring Bean 创建流程图 大家总是会错误的理解Bean的“实例化”和“初始化”过程,总会以为初始化就是对象执行构造函数生成对象实例的过程,其实不然,在初始化阶段实际对象已经实例化出来了,初始化阶段进行的是依赖的注入和执行一…...
重学SpringBoot3-怎样优雅停机
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-怎样优雅停机 1. 什么是优雅停机?2. Spring Boot 3 优雅停机的配置3. Tomcat 和 Reactor Netty 的优雅停机机制3.1 Tomcat 优雅停机3.2 Reac…...
【数据结构】顺序表和链表
1.线性表 我们在C语言当中学过数组,其实呢,数组可以实现线性表,线性表理解上类似于数组,那么什么是线性表呢?线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构,常见…...
Training language models to follow instructions with human feedback解读
前置知识方法数据集结论 前置知识 GPT的全称是Generative Pre-Trained Transformer,预训练模型自诞生之始,一个备受诟病的问题就是预训练模型的偏见性。因为预训练模型都是通过海量数据在超大参数量级的模型上训练出来的,对比完全由人工规则…...
线性回归矩阵求解和梯度求解
正规方程求解线性回归 首先正规方程如下: Θ ( X T X ) − 1 X T y \begin{equation} \Theta (X^T X)^{-1} X^T y \end{equation} Θ(XTX)−1XTy 接下来通过线性代数的角度理解这个问题。 二维空间 在二维空间上,有两个向量 a a a和 b b b&…...
M3U8不知道如何转MP4?包能学会的4种格式转换教学!
在流媒体视频大量生产的今天,M3U8作为一种基于HTTP Live Streaming(HLS)协议的播放列表格式,广泛应用于网络视频直播和点播中。它包含了媒体播放列表的信息,指向了视频文件被分割成的多个TS(Transport Stre…...
C++第4课——swap、switch-case-for循环(含视频讲解)
文章目录 1、课程代码2、课程视频 1、课程代码 #include<iostream> using namespace std; int main(){/* //第一个任务:学会swap int a,b,c;//从小到大排序输出 升序 cin>>a>>b>>c;//5 4 3if(a>b)swap(a,b);//4 5 3 swap()函数是用于交…...
大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 4)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌
学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...
linux网络编程7——协程设计原理与汇编实现
文章目录 协程设计原理与汇编实现1. 协程概念2. 协程的实现2.1 setjmp2.2 ucontext2.3 汇编实现2.4 优缺点2.5 实现协程原语2.5.1 create()2.5.2 yield()2.5.3 resume()2.5.4 exit()2.5.5 switch()2.5.6 sleep() 2.6 协程调度器 3. 利用hook使用协程版本的库函数学习参考 协程设…...
Ubuntu22.04版本左右,扩充用户可使用内存
1 取得root权限后,输入命令 lsblk 查看所有磁盘和分区,找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区,并转为所需要的分区数据类型,先进行分区与格式化,过程自行查阅。 扩充替换过程,例如…...
基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)
注:图片来源于公众号,公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具,本人最近正在读一本书《ArcGIS Python 编程基础与应用》,在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...
网上商城平台建设/青海网站seo
接上篇的线性回归文章,传送门如下。Python数据科学:线性回归多元线性回归的前提条件:因变量不能和扰动项有线性关系自变量与因变量之间要有线性关系自变量之间不能有太强的线性关系扰动项或残差独立且应服从均值为0、方差一定的正态分布/ 01 …...
大连网站建设外贸/网络安全培训机构排名
全书链接: 线性代数...
制作网站电话/权重查询爱站网
News计算机与网络英语词汇(C3)Source:2008-07-19我要投稿论坛 Favoritechip 晶 片chip carriei 芯片载体chip device configuration 芯片装置组态chip enable(CE) 切片便能chip LSI technology 芯片大型集体化工艺学chip materials 芯片材料chip memory 芯片记忆(器)chip mic…...
河北固安县网站建设/360竞价推广怎么做
NiceProgressBar项目地址:flyou/NiceProgressBar 简介:NiceProgressBar 好用的圆形进度条组件更多:作者 提 Bug 标签:圆形进度条-a nice progressbar for androidstep 1Add it in your root build.gradle at the end of repos…...
昆山推广用什么网站比较好/谷歌浏览器下载安装2021最新版
http://www.android100.net/html/201402/22/5690.html转载于:https://www.cnblogs.com/u3shadow/p/4298346.html...
手工制作花朵/湖北seo服务
最新的FineUI QQ交流群请看这里:http://fineui.com/qqgroup/如果你喜爱ExtAspNet,可以通过如下QQ群和大家交流:ExtAspNet技术交流群1:158028499(已饱和),100人,创始人:sanshi ExtAspNet技术交流…...