【多线程】阻塞队列详解及实现(模拟实现生产者消费者模型)

阻塞队列

  • 🌴生产者消费者模型
    • 🌸强耦合
    • 🌸松耦合(解耦合)
  • 🎍Java标准库中的阻塞队列
  • 🌳阻塞队列的模拟实现
  • ⭕总结

阻塞队列是什么?

  • 阻塞队列是⼀种特殊的队列. 也遵守 “先进先出” 的
  • 阻塞队列能是⼀种线程安全的数据结构, 并且具有以下特性:

• 当队列满的时候, 继续⼊队列就会阻塞, 直到有其他线程从队列中取⾛元素.
• 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插⼊元素.

阻塞队列的⼀个典型应⽤场景就是 “⽣产者消费者模型”. 这是⼀种⾮常典型的开发模型.

🌴生产者消费者模型

什么是生产者消费者模型

  • ⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。
  • ⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取.

什么是耦合与解耦?

  • 耦合是两个或多个模块之间的相互关联。在软件工程中,两个模块之间的耦合度越高,维护成本越高。因此,在系统架构的设计过程中,应减少各个模块之间的耦合度,以提高应用的可维护性

耦合又分为紧耦合(强耦合)和 松耦合(解耦合)

🌸强耦合

紧耦合架构本质是Client/Server的模型,如下图所示
在这里插入图片描述

优点是:架构简单、设计简单、开发周期短、能够快速的开发、投入、部署、应用。

但随着集群规模的扩大,系统的稳定性逐渐变差,主要原因如下:

  • 同步操作导致对网络资源消耗大。同步操作在数据发送和数据返回之间,有很大一段是空闲的,这种空闲占用是对网络资源的极大浪费。

  • 安全控制力度差,因为服务器直接暴露给客户机,容易引发网络攻击行为。

  • 程序代码之间关联度过高,不利于模块化处理。

🌸松耦合(解耦合)

松耦合架构本质上是在client/server模型之间加入一个代理,把CS模型变成CAS模型。 在新的架构下,客户机的角色不变,代理服务器承担起与客户机的通信,和对客户机的识别判断工作,服务器位于代理服务器后面,对客户机来说不可见,它只负责数据处理工作,另外我们也把CS模型的同步操作改为CAS的代理处理。 如下图所示:

在这里插入图片描述

优点如下:

  • 多任务并行处理能力获得极大提升。

  • 实现负载自适应机制(根据当时运行环境,松耦合架构分配并行工作任务,避免超载现象)。

  • 基本杜绝了对Server服务端的网络攻击行为,由于代理服务器的隔绝和筛查作用, 同时结合其它安全管理手段,外部攻击在代理服务器处就被识别和过滤掉了,这样就保护了后面的服务器不受影响。

  • 异步操作减少了网络资源消耗和操作关联。

  • 提高了系统的可维护性。

了解了耦合之后,我们就可以通过一个阻塞队列来实现一个生产者消费者的模型

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

在这个模型里面

  • 阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒. (削峰填⾕)

⽐如在 “秒杀” 场景下, 服务器同⼀时刻可能会收到⼤量的⽀付请求. 如果直接处理这些⽀付请求, 服务器可能扛不住(每个⽀付请求的处理都需要⽐较复杂的流程). 这个时候就可以把这些请求都放到⼀个阻塞队列中, 然后再由消费者线程慢慢的来处理每个⽀付请求.这样做可以有效进⾏ “削峰”, 防⽌服务器被突然到来的⼀波请求直接冲垮.

  • 阻塞队列也能使⽣产者和消费者之间 解耦

⽐如过年⼀家⼈⼀起包饺⼦. ⼀般都是有明确分⼯, ⽐如⼀个⼈负责擀饺⼦⽪, 其他⼈负责包. 擀饺⼦⽪的⼈就是 “⽣产者”, 包饺⼦的⼈就是 “消费者”.擀饺⼦⽪的⼈不关⼼包饺⼦的⼈是谁(能包就⾏, ⽆论是⼿⼯包, 借助⼯具, 还是机器包), 包饺⼦的⼈也不关⼼擀饺⼦⽪的⼈是谁(有饺⼦⽪就⾏, ⽆论是⽤擀⾯杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的)

🎍Java标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在⼀些程序中使⽤阻塞队列, 直接使⽤标准库中的即可.

  • BlockingQueue 是⼀个接⼝. 真正实现的类是 LinkedBlockingQueue.
  • put ⽅法⽤于阻塞式的⼊队列, take ⽤于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些⽅法不带有阻塞特性.
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// ⼊队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

使用阻塞队列模拟生产者消费者模型

public class Demo {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();Thread customer = new Thread(() -> {while (true) {try {int value = blockingQueue.take();System.out.println("消费元素: " + value);} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者");customer.start();Thread producer = new Thread(() -> {Random random = new Random();while (true) {try {int num = random.nextInt(1000);System.out.println("⽣产元素: " + num);blockingQueue.put(num);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}, "⽣产者");producer.start();customer.join();producer.join();}}

🌳阻塞队列的模拟实现

思路
• 通过 “循环队列” 的⽅式来实现.
• 使⽤ synchronized 进⾏加锁控制.
• put 插⼊元素的时候, 判定如果队列满了, 就进⾏ wait. (注意, 要在循环中进⾏ wait. 被唤醒时不⼀定
队列就不满了, 因为同时可能是唤醒了多个线程).
• take 取出元素的时候, 判定如果队列为空, 就进⾏ wait. (也是循环 wait)

这里博主选择的是用数组的形式进行模拟实现

首先我们创建一个 “循环队列” ,关于循环队列了解的小伙伴,可以去看看博主相应的博客栈和队列

public class BlockingQueue {private int[] items = new int[1000];private volatile int size = 0;private volatile int head = 0;private volatile int tail = 0;

上述代码只是一个简单的环形队列,如果在多线程中进行操作的话,会出现线程安全问题,所以接下来我们要做的是就是解决上述线程安全问题

  • 首先呢。我们要保证同一个对象,在出队列时不能入队列,在入队列时不能出队列
    所以我们使用 synchronized 进行加锁控制

  • 其次。put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

所以我们在判断为满或者为空时,使用while循环进行判断。

代码实现如下:

public class MyBlockingQueue {private int[] items = new int[1000];private int head = 0;private int tail = 0;private int size = 0;// 入队列public void put(int value) throws InterruptedException {synchronized (this) {while (size == items.length) {// 队列满了, 此时要产生阻塞.// return;this.wait();}items[tail] = value;tail++;if (tail >= items.length) {tail = 0;}size++;// 这个 notify 唤醒 take 中的 waitthis.notify();}}// 出队列public Integer take() throws InterruptedException {int result = 0;synchronized (this) {while (size == 0) {//return null;// 队列空, 也应该阻塞.this.wait();}result = items[head];head++;if (head >= items.length) {head = 0;}size--;// 唤醒 put 中的 waitthis.notify();}return result;}
}

测试代码如下:

public class ThreadDemo2 {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();Thread customer = new Thread(() -> {while (true) {try {int result = queue.take();System.out.println("消费: " + result);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();Thread producer = new Thread(() -> {int count = 0;while (true) {try {System.out.println("生产: " + count);queue.put(count);count++;Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();}
}

⭕总结

关于《【多线程】阻塞队列详解及实现(模拟实现生产者消费者模)》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

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

相关文章

四信AI智能识别及计量监测设备,助力入河入海排污口规范化建设

随着城市化和工业化的快速发展&#xff0c;污水排放已成为主要的环境问题之一。2022年&#xff0c;国务院办公厅发布《关于加强入河入海排污口监督管理工作的实施意见》&#xff0c;提出“加强科技研发&#xff0c;开展各类遥感监测、水面航测、水下探测、管线排查等实用技术和…

游戏配置内存“瘦身”策略

背景 游戏配置数据绝对是游戏服务器进程的内存大头,有些游戏服务器单纯数据配置的容量就超过一个G。因此,这部分内存优化也就放在首要位置了。 优化策略 在《服务器进程如何降低内存》一文中,我们讲述了可以通过“优化游戏配置缓存”来降低游戏服务器进程的内存使用量。本…

知乎万赞:为什么我不建议你转行学python?_为什么不建议学python

写在前面 本文的目的很简单&#xff0c;一句话&#xff1a; 用最少的时间&#xff0c;最高效率&#xff0c;让你清楚&#xff1a;想要拿到python offer&#xff0c;你需要做什么&#xff1f;你该怎么做&#xff1f;如果你不具备这些条件&#xff0c;我不建议你转行学python&a…

Stable Diffusion 3重磅发布

刚不久&#xff0c;Stability AI发布了Stable Diffusion 3.0&#xff0c;这一版本采用了与备受瞩目的爆火Sora相同的DiT架构。通过这一更新&#xff0c;画面质量、文字渲染以及对复杂对象的理解能力都得到了显著提升。由于这些改进&#xff0c;先前的技术Midjourney和DALL-E 3在…

【并发】CAS原子操作

1. 定义 CAS是Compare And Swap的缩写&#xff0c;直译就是比较并交换。CAS是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令&#xff0c;这个指令会对内存中的共享数据做原子的读写操作。其作用是让CPU比较内存中某个值是否和预期的值相同&#xff0c;如果相…

springboot217志同道合交友网站

springboot志同道合交友网站设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本志同道合交友网站就是在这样的大环境下诞生&#xff0c;其可以帮助…

内网穿透教程-SSH连接访问服务器主机(手把手教学)

内网穿透教程-用于SSH连接访问服务器主机 访问量子互联官网&#xff08;用于内网穿透的工具-需付费&#xff09; 打开官网&#xff1a; https://console.uulap.com/ 访问控制台&#xff1a; 可直接查看内网穿透新手使用教程&#xff1a;http://doc.uulap.com/docs/nattunnel/s…

大模型实战营第二期——4. XTuner 大模型单卡低成本微调实战

github地址&#xff1a;InternLM/tutorial-书生浦语大模型实战营文档地址&#xff1a;XTuner 大模型单卡低成本微调实战视频地址&#xff1a;XTuner 大模型单卡低成本微调实战Intern Studio: https://studio.intern-ai.org.cn/console/instance 这个人的研究方向是眼科的AI&am…

AI Agent深入浅出——以ERNIE SDK和多工具智能编排为例

在过去一年里&#xff0c;通用大语言模型&#xff08;LLM&#xff09;的飞速发展引起了全球的关注。百度等科技巨头推出了各自的大模型&#xff0c;不断提高语言模型性能的上限。然而&#xff0c;业界对LLM所设定的目标不再局限于基本的问答功能&#xff0c;而是寻求利用大模型…

什么是MapReduce

1.1 MapReduce到底是什么 Hadoop MapReduce是一个软件框架&#xff0c;基于该框架能够容易地编写应用程序&#xff0c;这些应用程序能够运行在由上千个商用机器组成的大集群上&#xff0c;并以一种可靠的&#xff0c;具有容错能力的方式并行地处理上TB级别的海量数据集。这个定…

注册中心 Service Discovery --- Intro

注册中心 Service Discovery --- Intro 为什么需要注册中心注册中心的原理常用的注册中心注册中心的高可用 为什么需要注册中心 在微服务架构中&#xff0c;系统被拆分成了若干个独立的服务&#xff0c;因此服务之间需要进行通信和协作。为了实现服务的发现和调用&#xff0c;需…

C#,动态规划(DP)模拟退火(Simulated Annealing)算法与源代码

1 模拟退火 *问题:**给定一个成本函数f:r^n–>r*&#xff0c;找到一个 n 元组&#xff0c;该元组最小化 f 的值。请注意&#xff0c;最小化函数值在算法上等同于最大化(因为我们可以将成本函数重新定义为 1-f)。 很多有微积分/分析背景的人可能都熟悉单变量函数的简单优化。…

Llama2模型的优化版本:Llama-2-Onnx

Llama2模型的优化版本&#xff1a;Llama-2-Onnx。 Llama-2-Onnx是Llama2模型的优化版本。Llama2模型由一堆解码器层组成。每个解码器层&#xff08;或变换器块&#xff09;由一个自注意层和一个前馈多层感知器构成。与经典的变换器相比&#xff0c;Llama模型在前馈层中使用了不…

YOLOv5算法进阶改进(16)— 更换Neck网络之GFPN(源自DAMO-YOLO)

前言:Hello大家好,我是小哥谈。GFPN(Global Feature Pyramid Network)是一种用于目标检测的神经网络架构,它是在Faster R-CNN的基础上进行改进的,旨在提高目标检测的性能和效果。其核心思想是引入全局特征金字塔,通过多尺度的特征融合来提取更丰富的语义信息。具体来说,…

用Python实现创建十二星座数据分析图表

下面小编提供的代码中&#xff0c;您已经将pie.render()注释掉&#xff0c;并使用了pie.render_to_file(十二星座.svg)来将饼状图渲染到一个名为十二星座.svg的文件中。这是一个正确的做法&#xff0c;如果您想在文件中保存图表而不是在浏览器中显示它。 成功创建图表&#xf…

贪心算法---前端问题

1、贪心算法—只关注于当前阶段的局部最优解,希望通过一系列的局部最优解来推出全局最优----但是有的时候每个阶段的局部最优之和并不是全局最优 例如假设你需要找给客户 n 元钱的零钱&#xff0c;而你手上只有若干种面额的硬币&#xff0c;如 1 元、5 元、10 元、50 元和 100…

李宏毅2023机器学习作业1--homework1——python语法

# 定义list del_col del_col [0, 38, 39, 46, 51, 56, 57, 64, 69, 74, 75, 82, 87] # 删除raw_x_train中del_col的列&#xff0c;axis为1代表删除列 raw_x_train np.delete(raw_x_train, del_col, axis1) # numpy数组增删查改方法 # 定义列表get_col get_col [35, 36, 37,…

vector 用法

C++数组是继承C语言的,C++标准库中的vector封装了动态数组,是一个模板类(vector<int>,<>里面可以是各种类型。 定义方式: vector<元素类型> 对象名(长度); (注:vector还有个好处就是,数组定义时长度那里不能包含变量,但是vector定义时长度那里可…

2.23 Qt day4 事件机制+定时器事件+键盘事件+鼠标事件

思维导图&#xff1a; 做一个闹钟&#xff0c;在行编辑器里输入定闹钟的时间&#xff0c;时间到了就语音播报文本里的内容&#xff0c;播报五次 widget.h&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QDebug>//输出类 #include<…

网络攻防之ARP欺骗和DNS劫持实验

目录 ARP单向欺骗 ARP双向欺骗 DNS劫持 实验环境&#xff1a; 攻击主机&#xff1a;kali2023虚拟机&#xff0c;IP地址为192.168.133.141 靶机&#xff1a;Windows10虚拟机&#xff0c;IP地址为192.168.133.129 网关地址&#xff1a;192.168.133.2 (1)ARP协议介绍 在以…