当前位置: 首页 > article >正文

【多线程初阶】阻塞队列 生产者消费者模型

文章目录

  • 一、阻塞队列
  • 二、生产者消费者模型
    • (一)概念
    • (二)生产者消费者的两个重要优势(阻塞队列的运用)
      • 1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)
      • 2) 削峰填谷
    • (三)生产者消费者模型付出的代价
  • 三、标准库中的阻塞队列
    • (一)观察模型的运行效果
    • (二)观察阻塞效果
      • 1) 队列为空的阻塞效果
      • 2) 队列为满的阻塞效果
  • 四、模拟实现阻塞队列
    • 1) 实现要点
    • 2) 基于数组实现普通队列
    • 3) 添加所需字段
    • 4) 循环队列逻辑
    • 5) 实现 put()
    • 6) 实现take()
    • 7) 对 put 和 take 实现阻塞效果

一、阻塞队列

在数据结构学习集合类是我们接触了队列、优先级队列,都是一些很重要的数据结构,尤其是现在搞后端开发,经常会使用分布式系统,微服务框架等等

  • 阻塞队列,是一种更加复杂的队列
  • 也遵守"先进先出"的原则
  • 阻塞队列是一种线程安全的数据结构
  • 阻塞特性:
    1. 队列为空时,尝试出队列,出队列操作就会阻塞,阻塞到其他线程添加元素为止
    1. 队列为满时,尝试入队列,入队列操作就会阻塞,阻塞到其他线程取走元素为止

阻塞队列的一个典型应用场景就是"生产者消费者模型",这是一种非常典型的开发模型

在这里插入图片描述

二、生产者消费者模型

(一)概念

  • 生产者消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题
  • 生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

对于上述描述,举个例子,方便理解~~
请出我们的老三位,朝新,朝望,小舟,三个人包饺子

  • 两个包饺子的方案
    1. 三个人,每个人都分别擀一个饺子皮,包一个饺子
  • 这个方案三个线程就会竞争同一个资源 =>擀面杖,造成阻塞等待
    1. 朝新专门负责擀饺子皮,另外两个人负责包饺子

在这里插入图片描述

  • 当朝新擀饺子皮,擀得飞起~~,会出现阻塞太多了,消费者来不及消费,造成桌子上没地方放饺子皮了,就阻塞了
  • 诶? 上面第一个方案就是因为阻塞等待才pass的,这里产生阻塞怎么就行呢?

这里我们提到的阻塞是一个"极端情况",生产者消费者之间速度不协调才会出现,一般情况下,都是不会出现阻塞的

(二)生产者消费者的两个重要优势(阻塞队列的运用)

    1. 解耦合 : 阻塞队列也能是生产者和消费者之间解耦,降低耦合是为了让后续修改的时候成本低~
    1. 削峰填谷 : 阻塞队列相当于一个缓冲区,平衡了生产者和消费者的处理能力

比如在"秒杀"场景下,服务器同一时刻可能会收到大量的支付请求,如果直接处理这些支付请求,服务器可能扛不住(每个支付请求处理都需要比较复杂的流程),这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢的按照自己的节奏来处理每个支付请求,不至于下游服务器直接崩溃
这样做可以有效进行"削峰",防止服务器被突然到来的一波请求直接冲垮

1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)

在这里插入图片描述

当我们在两个服务器之间引入 阻塞队列后

在这里插入图片描述

  • 本来是 A 和 B耦合,现在是 A和队列耦合,B和队列耦合
  • 同样是耦合,为什么单单与队列耦合,就是我们所希望的呢?
  • 因为队列的作用基本上是入队列,出队列,功能单一,固定,一般不会涉及到修改,大大降低了耦合,后续修改服务器代码,成本低

2) 削峰填谷

在这里插入图片描述
这张图可以理解为服务器收到的请求量的曲线图

在这里插入图片描述

  • 一般来说 A这种上游的服务器,尤其是 入口的服务器,干的活更简单,单个请求消耗的资源数少

  • 像 B这种下游的服务器,同行承担更重的任务量,复杂的计算/存储 工作,单个请求消耗的资源数更多

  • 日常工作中,确实会给 B这样角色的服务器分配更好的机器,即使如此,也很难保证 B 承担的访问量能够比 A更高

  • 服务器会什么会挂呢? 比如每次选课时,选课系统就会卡的进不去

  • 服务器每个请求的时候,都是需要消耗一定的硬件资源

  • 包括不限于 CPU,内存,硬盘,网络带宽的资源

  • 同时有N个请求呢? 消耗的量*N

  • 一旦消耗的总量,超出了机器硬件资源的上限,此时,对应的进程就可能崩溃或者操作系统产生卡顿 =>挂了

  • 提供的量 < 消耗的量 ,就会挂~

当我们在两个服务器之间引入 阻塞队列后

在这里插入图片描述

  • 一般来说,请求量激增是突发,时间也会短
  • 趁着峰值过去了,B 仍然继续消费数据,利用波谷的时间,阿里赶紧消费之前积压的数据

(三)生产者消费者模型付出的代价

    1. 引入队列后,整体的结构会更复杂,此时,就需要更多的机器,进行部署,生产环境的结构会更复杂,管理起来更加麻烦
    1. 运用阻塞队列,导致性能降低,效率会有影响

在这里插入图片描述

三、标准库中的阻塞队列

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

  • BlockingQueue 是一个接口,真正实现的类是 LinkedBlockingQueue
  • put方法用于阻塞式的入队列,take用于阻塞式出队列
  • BlockingQueue 也有offer ,poll,peek等方法,但是这些方法不带有阻塞特性

在这里插入图片描述

public class Demo29 {public static void main(String[] args) throws InterruptedException{BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);for (int i = 0; i < 100;i++){queue.put("a");}System.out.println("队列已满");queue.put("a");System.out.println("再次尝试 put 元素");}
}

使用jconsole观察线程状态

在这里插入图片描述
在这里插入图片描述

  • 其中我们添加了阻塞队列的参数capacity =>最多能容纳多少元素
  • 如果不设置capacity,默认是一个非常大的值
  • 实际开发中,一般建议大家能够设置上要求的最大值
  • 否则,队列可能变得非常大,导致把内存耗尽,产生内存超出范围这样的异常

在这里插入图片描述

  • 填满也没事,队列最多21亿个数据,每个元素是一个int(4个字节)
  • 极端情况 80亿个字节 => 8G,打满了消耗 8GB内存
  • 我们电脑都是 16G 32G 倒是能消耗得起
  • 不过会影响效率,最好还是设置capacity
  • 一个JVM进程也不一定能够利用机器所有的内存,是可以在运行JVM的时候通过一定的参数指定JVM最多消耗多少内存
  • 如果实际消耗的内存,超过了JVM运行时候的限制上限,确实会挂~
  • Thousand 千 => K
  • Million 百万 => M
  • Billion 十亿 => G

(一)观察模型的运行效果

public class Demo30 {// 生产者一个线程  消费者一个线程public static void main(String[] args) {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();Thread producer = new Thread(() -> {int n = 0;while (true) {try {queue.put(n);//Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("生产元素 " + n);n++;}}, "producer");Thread consumer = new Thread(() -> {while(true){try {Integer n = queue.take();System.out.println("消费元素 " + n);//Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}, "consumer");producer.start();consumer.start();}
}

在这里插入图片描述

  • 通过打印的日志信息,可以观察到,两个线程的执行速度旗鼓相当,并没有产生阻塞
  • 这就是开发中的典型情况,虽然模型是会出现阻塞的,但是只要我们协调好生产和消费的速度,两个线程执行速度相差不大,程序就会高效的运行

(二)观察阻塞效果

1) 队列为空的阻塞效果

  • 上述的 producer 和 consumer 两个线程的速度旗鼓相当,很难观察到阻塞
  • 我们添加sleep,使得两线程速度相差较大,来观察阻塞队列产生的阻塞效果

在这里插入图片描述
通过降低producer调用 put() 的速度,阻塞队列中的元素被消耗的速度远远大于生产的速度,进而使阻塞队列对 consumer的take() 产生阻塞效果

2) 队列为满的阻塞效果

  • 降低 consumer的消费速度,观察阻塞队列对producer的阻塞效果

在这里插入图片描述

  • 虽然 producer 只执行了1秒,但已经把阻塞队列填满了
  • 因为队列已满,producer的put()方法产生阻塞效果
  • 所以只能consumer消费一个元素,生产者才能生产一个元素

四、模拟实现阻塞队列

我们自己模拟实现一个简单的阻塞队列,并且基于这个阻塞队列实现 生产者消费者模型

1) 实现要点

  • 通过"循环队列"的方式来实现
  • 使用synchronized进行加锁控制
  • put 插入元素时,判定如果队列满了,就进行wait
  • take 取出元素时,判定如果队列为空,就进行wait
  • wait的使用需要注意,建议配合while使用(后面详细介绍)

2) 基于数组实现普通队列

class MyBlockingQueue{private  String[] data = null;public MyBlockingQueue(int capacity){data = new String[capacity];}
}public class Demo31 {public static void main(String[] args) {}
}

3) 添加所需字段

class MyBlockingQueue{private  String[] data = null;private int head = 0;//队头private int tail = 0;//队尾private int size = 0;//元素个数public MyBlockingQueue(int capacity){data = new String[capacity];}
}

4) 循环队列逻辑

  • put()元素 放入tail处,take()元素 head处取出
  • put()元素 => tail++
  • take()元素 =>head++
  • 若 head 和 tail > data.length =>两个指针置为0,继续循环
  • 若 head 和 tail 指向同一个元素,要么队空,要么队满
  • 要么 所有线程阻塞在 put,要么所有线程阻塞在take

5) 实现 put()

  • 队满要进入阻塞(如何进入阻塞)
  • put()一次,tail++ size ++
  • tail 走出队列 要置为0
 public void put(String elem){if(size == data.length){//队列满了,进入阻塞return;}data[tail] = elem;tail++;if(tail >= data.length){tail = 0;}size++;}
}

6) 实现take()

  • 队空要进入阻塞(如何进入阻塞)
  • take()一次 head++ size–
  • head走出队列 要置为0
public  String take(){if(size == 0){//队列为空,进入阻塞return null;}String ret = data[head];head++;if(head == data.length){head = 0;}size--;return ret;}
}

7) 对 put 和 take 实现阻塞效果

在这里插入图片描述

相关文章:

【多线程初阶】阻塞队列 生产者消费者模型

文章目录 一、阻塞队列二、生产者消费者模型(一)概念(二)生产者消费者的两个重要优势(阻塞队列的运用)1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)2) 削峰填谷 (三)生产者消费者模型付出的代价 三、标准库中的阻塞队列(一)观察模型的运行效果(二)观察阻塞效果1) 队…...

《100天精通Python——基础篇 2025 第5天:巩固核心知识,选择题实战演练基础语法》

目录 一、踏上Python之旅二、Python输入与输出三、变量与基本数据类型四、运算符五、流程控制 一、踏上Python之旅 1.想要输出 I Love Python,应该使用()函数。 A.printf() B.print() C.println() D.Print() 在Python中想要在屏幕中输出内容&#xff0c;应该使用print()函数…...

机器人夹爪的选型与ROS通讯——机器人抓取系统基础系列(六)

文章目录 前言一、夹爪的选型1.1 任务需求分析1.2 软体夹爪的选型 二、夹爪的ROS通讯2.1 夹爪的通信方式介绍2.2 串口助手测试2.3 ROS通讯节点实现 总结Reference: 前言 本文将介绍夹爪的选型方法和通讯方式。以鞋子这类操作对象为例&#xff0c;将详细阐述了对应的夹爪选型过…...

第二十八章 RTC——实时时钟

第二十八章 RTC——实时时钟​​​​​​​ 目录 第二十八章 RTC——实时时钟 1 RTC实时时钟简介 2 RTC外设框图剖析 3 UNIX时间戳 4 与RTC控制相关的库函数 4.1 等待时钟同步和操作完成 4.2 使能备份域涉及RTC配置 4.3 设置RTC时钟分频 4.4 设置、获取RTC计数器及闹钟 5 实时时…...

使用 DuckLake 和 DuckDB 构建 S3 数据湖实战指南

本文介绍了由 DuckDB 和 DuckLake 组成的轻量级数据湖方案&#xff0c;旨在解决传统数据湖&#xff08;如HadoopHive&#xff09;元数据管理复杂、查询性能低及厂商锁定等问题。该方案为中小规模数据湖场景提供了简单、高性能且无厂商锁定的替代选择。 1. 什么是 DuckLake 和 D…...

大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系

文章目录 前言&#xff1a;为什么提示词工程成为AI时代的核心技能一、提示词的本质探源&#xff1a;认知科学与逻辑学的理论基础1.1 认知科学视角下的提示词本质信息处理理论的深层机制图式理论的实际应用认知负荷理论的优化策略 1.2 逻辑学框架下的提示词架构形式逻辑的三段论…...

如何基于Mihomo Party http端口配置git与bash命令行代理

如何基于Mihomo Party http端口配置git与bash命令行代理 1. 确定Mihomo Party http端口配置 点击内核设置后即可查看 默认7892端口&#xff0c;开启允许局域网连接 2. 配置git代理 配置本机代理可以使用 127.0.0.1 配置局域网内其它机代理需要使用本机的非回环地址 IP&am…...

CMake 为 Debug 版本的库或可执行文件添加 d 后缀

在使用 CMake 构建项目时,我们经常需要区分 Debug 和 Release 构建版本。一个常见的做法是为 Debug 版本的库或可执行文件添加后缀(如 d),例如 libmylibd.so 或 myappd.exe。 本文将介绍几种在 CMake 中实现为 Debug 版本自动添加 d 后缀的方法。 方法一:使用 CMAKE_DEBU…...

Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit

Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit 在Linux权限系统中,除了基本的读、写(w)、执行(x)权限外,还有三个特殊权限位:SetUID、SetGID和Sticky Bit。这些权限位提供了更精细的权限控制机制,尤其在需要临时提升权限或管理共享资源时非常有用。 一、SetUID (s位…...

埃文科技智能数据引擎产品入选《中国网络安全细分领域产品名录》

嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》&#xff0c;埃文科技智能数据引擎产品成功入选数据分级分类产品名录。 在数字化转型加速的今天&#xff0c;网络安全已成为企业生存与发展的核心基石&#xff0c;为了解这一蓬勃发展的产业格局&#xff0c;嘶吼安全产业…...

使用VTK还是OpenGL集成到qt程序里哪个好?

在Qt程序中集成VTK与OpenGL&#xff1a;选择哪个更好&#xff1f; 在Qt程序中实现三维可视化时&#xff0c;开发者常常面临一个选择&#xff1a;是使用VTK&#xff08;Visualization Toolkit&#xff09;还是OpenGL&#xff08;Open Graphics Library&#xff09;。这两种技术…...

Java-IO流之打印流详解

Java-IO流之打印流详解 一、打印流概述1.1 什么是打印流1.2 打印流的特点1.3 打印流的应用场景 二、PrintStream详解2.1 基本概念2.2 构造函数2.3 核心方法2.4 使用示例 三、PrintWriter详解3.1 基本概念3.2 构造函数3.3 核心方法3.4 使用示例 四、PrintStream与PrintWriter的比…...

高效图像处理:使用 Pillow 进行格式转换与优化

高效图像处理:使用 Pillow 进行格式转换与优化 1. 背景引入 在图像处理应用中,格式转换、裁剪、压缩等操作是常见需求。Python 的 Pillow 库基于 PIL(Python Imaging Library),提供 轻量、强大 的图像处理能力,广泛用于 Web 开发、数据分析、机器学习 等领域。 本文将…...

Github 2025-06-06 Java开源项目日报Top10

根据Github Trendings的统计,今日(2025-06-06统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目10TypeScript项目1Java实现的算法集合:使用Gitpod.io进行编辑和贡献 创建周期:2883 天开发语言:Java协议类型:MIT LicenseStar数量…...

使用 Ansible 在 Windows 服务器上安装 SSL 证书

在本教程中&#xff0c;我将向您展示如何使用 Ansible 在 Windows 服务器上安装 SSL 证书。使用 Ansible 自动化 SSL 证书安装过程可以提高 IT 运营的效率、一致性和协作性。我将介绍以下步骤&#xff1a; 将 SSL 证书文件复制到服务器将 PFX 证书导入指定的存储区获取导入的证…...

厂区能源监控系统:网关赋能下的高效能源管理与环保监测

在现代工业生产领域&#xff0c;能源的有效利用与环境保护是企业实现可持续发展的两大关键要素。厂区能源监控系统借助先进的信息技术与自动化控制手段&#xff0c;对厂区内能源消耗及污水处理等核心环节展开实时监控与精细化管理。其中&#xff0c;御控网关作为系统关键枢纽&a…...

CentOS 7 如何安装llvm-project-10.0.0?

CentOS 7 如何安装llvm-project-10.0.0&#xff1f; 需要先升级gcc至7.5版本&#xff0c;详见CentOS 7如何编译安装升级gcc版本?一文 # 备份之前的yum .repo文件至 /tmp/repo_bak 目录 mkdir -p /tmp/repo_bak && cd /etc/yum.repo.d && /bin/mv ./*.repo …...

Cursor 1.0 的核心功能亮点及技术价值分析

Cursor 1.0 的核心功能亮点及技术价值分析 结合官方更新和开发者实测整理&#xff1a; &#x1f6e0;️ 一、BugBot&#xff1a;智能自动化代码审查 功能亮点&#xff1a;深度集成 GitHub&#xff0c;自动扫描 Pull Request&#xff08;PR&#xff09;中的潜在 Bug&#xff08;…...

软考 系统架构设计师系列知识点之杂项集萃(83)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;82&#xff09; 第150题 体系结构权衡分析方法&#xff08;Architecture Tradeoff Analysis Method&#xff0c;ATAM&#xff09;是一种常见的系统架构评估框架&#xff0c;该框架主要关注系统的…...

NLP学习路线图(二十六):自注意力机制

一、为何需要你&#xff1f;序列建模的困境 在你出现之前&#xff0c;循环神经网络&#xff08;RNN&#xff09;及其变种LSTM、GRU是处理序列数据&#xff08;如文本、语音、时间序列&#xff09;的主流工具。它们按顺序逐个处理输入元素&#xff0c;将历史信息压缩在一个隐藏…...

Unity3D仿星露谷物语开发60之定制角色其他部位

1、目标 上一篇中定制了角色的衬衫、手臂。 本篇中将定制角色其他部位的图形&#xff0c;包括&#xff1a;裤子、发型、皮肤、帽子等。 2、定制裤子 &#xff08;1&#xff09;修改ApplyCharacterCustomisation.cs脚本 我们需要设置一个输入框选择裤子的颜色。 // Select …...

C++动态链接库封装,供C#/C++ 等编程语言使用——C++动态链接库概述(总)

目录&#xff1a; 一、前言及背景1.1需求描述1.2常见编程语言对比1.3应用背景 二、C对外接口2.1C对外封装2.2基于目标平台封装接口形式 三、系列文章汇总 一、前言及背景 1.1需求描述 不同的编程语言&#xff0c;具有不同的编程生态环境&#xff0c;对于项目应用来说&#xff…...

Google机器学习实践指南(机器学习模型泛化能力)

&#x1f525; Google机器学习(14)-机器学习模型泛化能力解析 Google机器学习(14)-机器学习模型泛化原理与优化&#xff08;约10分钟&#xff09; 一、泛化问题引入 ▲ 模型表现对比&#xff1a; 假设森林中树木健康状况预测模型&#xff1a; 图1&#xff1a;初始模型表现 …...

MySQL性能调优:Mysql8高频面试题汇总

1&#xff0c;主键和唯一键有什么区别&#xff1f; 主键不能重复&#xff0c;不能为空&#xff0c;唯一键不能重复&#xff0c;可以为空。 建立主键的目的是让外键来引用。 一个表最多只有一个主键&#xff0c;但可以有很多唯一键 2&#xff0c;MySQL常用的存储引擎有哪些&…...

Neo4j 数据建模:原理、技术与实践指南

Neo4j 作为领先的图数据库,其核心优势在于利用图结构直观地表达和高效地查询复杂关系。其数据建模理念与传统关系型数据库截然不同,专注于实体(节点)及其连接(关系)。以下基于官方文档,系统阐述其建模原理、关键技术、实用技巧及最佳实践: 一、 核心原理:以关系为中心…...

【数据结构知识分享】顺序表详解

一、存储结构 物理相邻性&#xff1a; 若元素 a 和 b 逻辑相邻&#xff0c;则它们在内存中的地址也连续&#xff08;如 &a[i1] &a[i] sizeof(ElemType)&#xff09;。 内存布局x&#xff1a; 基地址 索引 元素大小&#xff0c;通过首地址直接计算任意位置地址。 …...

vue+elementUI+springboot实现文件合并前端展示文件类型

项目场景&#xff1a; element的table上传文件并渲染出文件名称点击所属行可以查看文件,并且可以导出合并文件,此文章是记录合并文档前端展示的帖子 解决方案&#xff1a; 后端定义三个工具类 分别是pdf,doc和word的excle的目前我没整 word的工具类 package com.sc.modules…...

高效绘制业务流程图!专业模板免费下载

在复杂的业务流程管理中&#xff0c;可视化工具已成为提升效能的核心基础设施。为助力开发者、项目经理及业务架构师高效落地流程标准化&#xff0c;本文将为你精选5套开箱即用的专业流程图模板。这些模板覆盖跨部门协作、电商订单、客户服务等高频场景&#xff0c;具备以下核心…...

Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)

文章目录 Spring Boot Prometheus 实现应用监控&#xff08;基于 Actuator 和 Micrometer&#xff09;环境准备示例结构启动和验证验证 Spring Boot 应用Prometheus 抓取配置&#xff08;静态方式&#xff09;Grafana 面板配置总结 Spring Boot Prometheus 实现应用监控&…...

PowerBI企业运营分析—列互换式中国式报表分析

PowerBI企业运营分析—列互换式中国式报表分析 欢迎来到Powerbi小课堂&#xff0c;在竞争激烈的市场环境中&#xff0c;企业运营分析平台成为提升竞争力的核心工具。 该平台通过高效整合多源数据&#xff0c;并实时监控关键指标&#xff0c;能够迅速揭示业务表现的全貌&#…...