ArrayBlockQueue原理分析

news2024/11/19 11:22:15

1.简介

它是带有生产者-消费者模式实现的并发容器,同样用来解决高并发场景下多线程之间数据共享的问题。Arra不支持扩缩容,其容量大小在初始化时就已经确定好了,尽管字面意义上来看它属于阻塞队列的一种,但它同时还提供了一些非阻塞式的API。
与之相类似的还有LinkedBlockQueue,二者主要的区别在于:

  1. ABQ的底层数据结构是数组,LBQ则为单向链表。
  2. ABQ是有界的,LBQ可有界可无界(也不完全是无界,只不过规定最大元素数为Integer.MAX_VALUE,但这近乎无穷大)。

2.实现原理

生产者-消费者模型。
图片来源:https://xie.infoq.cn/article/e96f60e81706699bf6f52bd5f
底层以数组作为阻塞队列。
全部读写查操作均由内部的ReentrantLock锁来负责实现同步。
通过Condition实现线程间的等待与唤醒操作。

采用Condition来进行线程的唤醒与休眠操作主要是为了达到选择性通知这一目的,因为在整个生产-消费模型中,对于线程的唤醒,我们要么是只唤醒消费者一方、要么只唤醒生产者一方,而不会将生产者消费者全部同时唤醒。

具体执行流程:

  • 若阻塞队列为空,则消费者一方阻塞等待,直至生产者一方将元素放到阻塞队列中以使阻塞队列非空。
  • 若阻塞队列已满,则生产者一方阻塞等待,直至消费者一方消费了若干个(≥ 1)元素以使阻塞队列不满。
  • 消费者消费元素(即从阻塞队列中取走一个元素)时,消费完毕会会唤醒生产者。
  • 生产者生产元素(即从阻塞队列中放置一个元素)时,生产完毕后会唤醒消费者。

3.源码分析

3.1 类定义

ArrayBlockQueue的类定义(JDK17)")
ArrayBlockQueue继承自AbstractQueue,而AbstractQueue实现了基本的增删改查操作,因此通过继承AbstractQueue,使得ArrayBlockQueue具有了队列的一些常见的基本操作。
此外,ArrayBlockQueue还实现了以下接口:

  • BlockingQueue:ABQ将具备阻塞队列的一些特性。
  • Serializable:可序列化,即可将该对象转换为字节流以实现持久化存储或网络传输。

ArrayBlockQueue的继承实现链(JDK17)")
可以看出,AbstractQueueArrayBlockQueue提供了基本的方法执行流程(执行模板),但部分子流程的执行细节(模板中的方法)并没有给出,并通过BlockQueue接口强制ArrayBlockQueue给出具体的实现。听上去,是不是有点模板方法模式的味道?

3.2 初始化

ArrayBlockQueue的构造方法源码(JDK17)")
可以看到,上下两个构造方法都调用了中间的那个构造方法,而且下面的那个构造方法仅仅就是在中间的构造方法基础上增加了挨个赋值给items数组的过程,因此,我们着重研究中间的那个构造方法。

执行流程

  1. 检查输入的capacity参数是否合法,不合法则抛出IAE异常。
  2. 初始化itemscapacity大小的Object数组
  3. 初始化lock,根据输入的fair参数来决定初始化为公平的还是非公平的ReentrantLock
  4. 然后接着上面的lock,去分别初始化 队列非空、队列不满 这两个Condition

3.3 add

ArrayBlockQueue的部分添加(生产)元素的方法(JDK17)元素的方法(JDK17)")
通过源码,我们发现add、offer、put方法的主要区别:

  • add:内部调用offer,不会发生阻塞,添加元素失败后(队列已满)抛异常。
  • offer:非阻塞式加锁添加元素,添加失败(发现元素个数 = 数组长度)则立刻返回false
  • put:阻塞式加锁添加元素,添加失败则一直会在notFullcondition上无限等待,直到被消费者唤醒或被外界打断(interrupt)。

这里的add方法实际上是父类AbstractQueue的,ArrayBlockQueueadd方法实际上是直接调用的父类的add方法,这里省略了。

如果符合添加元素的条件,则它们最终都会去调用enqueue方法:

  1. 根据下一个元素存放索引putIndex,将元素保存到items数组putIndex下标处。
  2. putIndex + 1,然后判断下次待添加元素的位置是否到达数组末端,若是,则重置为0(始端);否则,不做处理。
  3. 元素计数 + 1
  4. 唤醒阻塞在notEmptycondition的消费者。

这里之所以我们没在数组赋值之前进行下标合法性校验,是因为,在这个方法被调用之前,我们已经确保了各种条件均已符合预期,即,当前的各种参数如putIndexcountitems均是合法的,反过来说,正是在确保了这些参数的合法性之后,我们才可以去调用enqueue方法。

3.4 remove

ArrayBlockQueue的部分移除(消费)元素的方法(JDK17)元素的方法(JDK17)")
先分析一下takepoll这两个方法:

  • take:阻塞式加锁移除元素,移除失败则一直会在notEmptycondition上无限等待,直到被生产者唤醒或被外界打断(interrupt)。
  • poll:非阻塞式加锁移除元素,移除失败(发现元素个数 = 数组长度)则立刻返回null

如果符合消费条件(即队列中还有元素可取),则均进入dequeue方法:

  1. 获取到items数组takeIndex上对应的元素,然后将该位置的元素置为null
  2. takeIndex + 1,判断是否到达items末端,若是,则重置为0(始端);否则,不做处理。
  3. 元素计数 - 1
  4. 调用了内部类Ltr实例的elementDequeued方法,该方法的执行逻辑如下:
    1. 若此时元素计数为0,则意味着队列为空,清理容器内的全部引用和迭代器。
    2. takeIndex0,即发生了索引绕回,则清理发生takeIndex绕回或者其迭代器为nullNode节点上的引用,同时分离掉该节点,以避免内存泄漏。
  5. 唤醒阻塞在notFullcondition的生产者。

最后我们再来看一下remove方法:

  1. 待移除的对象为null或元素个数为0的情况下,直接返回false
  2. 根据takeIndex和putIndex的大小分两种情况进行遍历比较并移除:
    1. takeIndex < putIndex(正常情况,前人栽树后人乘凉),则直接一次性由takeIndex遍历至putIndex,比较并移除即可。
    2. takeIndex > putIndex(发生索引绕回的情况),则需要分两次遍历,第一次是由takeIndex 遍历至 length - 1,第二次是由0遍历至putIndex,然后比较并移除即可。

图片来源:https://developer.aliyun.com/article/1220031

上面的过程同样需要加锁,同时最终移除元素所调用的removeAt方法中将唤醒阻塞在notFullcondition的生产者。

3.5 get

ArrayBlockQueue的获取但不移除元素的方法(JDK17)")
执行流程很简单,先加锁,然后直接返回items数组takeIndex对应位置上的元素,再释放锁。

3.6 超时add/remove

image.png
你会发现,超时增删操作与普通增删操作逻辑上基本一致,仅有两处不同:

  1. 出现了while循环,只要增删条件不满足,将继续循环下去。
  2. condition上的无限等待调整为有限等待。

第二点好理解,毕竟有限等待的根本就是要保证锁的条件等待那里不能再像之前一样无限等待下去,可是为什么还要加一个while循环呢?

  1. 可能会出现暂时性死锁。不加while循环的情况下,意味着不管元素个数如何,生产者(举例)总会先进入有限等待状态,而消费者拿到锁后,却发现元素个数为0,同样也进入有限等待状态。也就是说,不加while循环得情况下,将会放大锁等待的条件范围,进而增加了暂时性死锁的风险。
  2. 你会发现,当我们被正常唤醒后,还会走一遍while循环进行元素个数的判断,这是因为可能存在虚假唤醒的情况,即生产者/消费者在没有收到明确通知的情况下就被唤醒了,如果此时不进行条件判断,那么生产者/消费者将错误地继续执行下去,从而产生不可预知的影响。

图片来源:https://javaguide.cn/java/collection/arrayblockingqueue-source-code.html

4.对比

项目ArrayBlockQueueLinkedBlockQueue
底层实现数组单向链表
是否有界有界(伪)无界
锁分离情况生产消费操作共用同一把锁消费操作用takeLock锁,生产操作用putLock
内存占用需要提前进行预分配,可能存在申请的内存空间比实际用到的内存空间大的情况无需预分配,但每个节点因存放指向下一节点的指针而占用了部分内存空间
项目ArrayBlockQueueConcurrentLinkedQueue
底层实现数组单向链表
是否有界有界(伪)无界
是否阻塞支持阻塞式和非阻塞式两种元素操作方式仅支持非阻塞式元素操作方式

参考文档

ArrayBlockingQueue 源码分析

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

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

相关文章

【python】python省市水资源数据分析可视化(源码+数据)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…

leetCode-hot100-数组专题之区间问题

数组专题之区间问题 知识点:解决思路:例题56.合并区间57.插入区间253.会议室 Ⅱ485.无重叠区间 数组区间问题是算法中常见的一类问题,它们通常涉及对数组中的区间进行排序、合并、插入或删除操作。无论是合并区间、插入区间还是删除重复空间&…

Windows10安装Docker Desktop - WSL update failed

按照提示更新wsl后,仍然会报错,github上没有找到解决方法。版本28、29、30都会报这个错。 经过尝试,将docker内的设置中,采用wsl禁掉即可。如下图:

【C语言回顾】联合和枚举

前言1. 联合体1.1 联合体的声明1.2 联合体的特点1.3 联合体的使用 2. 枚举2.1 枚举的声明2.2 枚举的特点2.3 枚举的使用 结语 #include<GUIQU.h> int main { 上期回顾: 【C语言回顾】结构体 个人主页&#xff1a;C_GUIQU 专栏&#xff1a;【C语言学习】 return 一键三连;…

大数据技术原理(二):搭建hadoop伪分布式集群这一篇就够了

&#xff08;实验一 搭建hadoop伪分布式&#xff09; -------------------------------------------------------------------------------------------------------------------------------- 一、实验目的 1.理解Hadoop伪分布式的安装过程 实验内容涉及Hadoop平台的搭建和…

有史以来最大的苹果手机?iPhone 16屏幕模组大升级

随着科技的不断进步&#xff0c;用户对于手机屏幕的需求也在不断提高。从最初的触控体验到如今的高分辨率、高刷新率&#xff0c;屏幕技术的发展日新月异。而据最新的消息显示&#xff0c;即将到来的iPhone 16系列将在屏幕模组上进行一次重大升级&#xff0c;有望成为有史以来最…

建模:Maya

一、常用按键 1、alt 左键 —— 环绕查看 2、alt 中键 —— 拖动模型所在面板 3、空格 —— 进入三视图模式&#xff1b;空格 左键按住拖动 —— 切换到对应视图 二、骨骼归零 1、T Pose 旋转模式&#xff0c;点击模型&#xff0c;摆好T姿势即可 2、复制模型设置200距离…

c++ queue容器

在C标准库中&#xff0c;std::queue 是一个容器适配器&#xff0c;它提供了队列&#xff08;FIFO - First In First Out&#xff09;的数据结构。队列是一种特殊的线性数据结构&#xff0c;只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端…

做抖音小店找带货达人合作为什么不成功呢?

大家好&#xff0c;我是喷火龙。 做抖音小店&#xff0c;和带货达人合作的模式&#xff0c;流量是最稳定的&#xff0c;爆单几率也是最大的&#xff0c;也是最适合新手商家的&#xff0c;想和带货达人合作&#xff0c;那肯定是得让达人带你的产品的。 但有些朋友把样品寄给达…

【Linux】-Linux文件的上传和下载、压缩和解压[9]

目录 前言 一、上传和下载 1、使用finalshell对Linux系统进行上传下载 2、rz、sz命令 二、解压和压缩 1、压缩格式 2、tar命令压缩 3、tar命令压缩 4、zip命令压缩文件 5、unzip命令解压文件 前言 在Linux系统中&#xff0c;文件的上传和下载、压缩和解压是非常重要…

面向浏览器端免费开源的三维可视化编辑器,包含BIM轻量化,CAD解析预览等特色功能。

ES 3DEditor &#x1f30d;Github地址 https://github.com/mlt131220/ES-3DEditor &#x1f30d;在线体验 https://editor.mhbdng.cn/#/ 基于vue3与ThreeJs&#xff0c;具体查看Doc 主要功能&#xff1a; 模型导入展示&#xff0c;支持OBJ、FBX、GLTF、GLB、RVT、IFC、SEA、3…

FedSyn: Synthetic Data Generation using Federated Learning

arxiv2022,没找到是哪个刊物的,是没投中吗? 这篇是用GAN做数据生成,每个client都训练一个生成器,加噪声传到server端聚合,实验是衡量生成图片的质量。 论文地址:arxiv code:没找到 贡献 提出了提出了一种新颖的方法(FedSyn ),将联邦学习、使用 GAN的合成数据生成…

使用 ASM 修改字段类型,解决闪退问题

问题 我的问题是什么&#xff1f; 在桥接类 UnityBridgeActivity 中处理不同 unity 版本调用 mUnityPlayer.destroy(); 闪退问题。 闪退日志如&#xff1a; 闪退日志说在 UnityBridgeActivity中找不到类型为 UnityPlayer 的属性 mUnityPlayer。 我们知道&#xff0c;Android…

深度学习之Pytorch框架垃圾分类智能识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着城市化进程的加快和人们环保意识的提高&#xff0c;垃圾分类已成为城市管理的重要一环。然而&am…

【Linux学习】进程

下面是有关进程的相关介绍&#xff0c;希望对你有所帮助&#xff01; 小海编程心语录-CSDN博客 目录 1. 进程的概念 1.1 进程与程序 1.2 进程号 2. 进程的状态 2.1 fork创建子进程 2.2 父子进程间的文件共享 3. 进程的诞生与终止 3.1 进程的诞生 3.2 进程的终止 1. 进…

[4]CUDA中的向量计算与并行通信模式

CUDA中的向量计算与并行通信模式 本节开始&#xff0c;我们将利用GPU的并行能力&#xff0c;对其执行向量和数组操作讨论每个通信模式&#xff0c;将帮助你识别通信模式相关的应用程序&#xff0c;以及如何编写代码 1.两个向量加法程序 先写一个通过cpu实现向量加法的程序如…

算法刷题day52:区间DP

目录 引言一、石子合并二、环形石子合并三、能量项链四、加分二叉树 引言 关于区间DP&#xff0c;我其实觉得核心思想就是把一个区间拆分为任意两个区间&#xff0c;相当于是模拟枚举全部这种区间组合的过程&#xff0c;然后从中寻求最优解&#xff0c;本质上的思想不难&#…

PLC工程师按这个等级划分是否靠谱?

在工业自动化领域&#xff0c;PLC工程师扮演着至关重要的角色&#xff0c;他们负责构建、维护自动化系统&#xff0c;推动工业4.0进程的发展。成为一名优秀的PLC工程师需要经历不同境界的发展阶段&#xff0c;每个阶段都对应着不同的技能要求和责任。以下是PLC工程师的六种级别…

必应bing国内推广开户,全方位必应广告开户流程介绍!

在所有获客渠道中&#xff0c;搜索引擎广告成为企业扩大品牌影响力、精准触达目标客户的关键途径之一。作为全球领先的搜索引擎之一&#xff0c;必应&#xff08;Bing&#xff09;拥有庞大的用户群体和独特的市场优势&#xff0c;是企业不可忽视的营销阵地。云衔科技&#xff0…

声音转文本(免费工具)

声音转文本&#xff1a;解锁语音技术的无限可能 在当今这个数字化时代&#xff0c;信息的传递方式正以前所未有的速度进化。从手动输入到触控操作&#xff0c;再到如今的语音交互&#xff0c;技术的发展让沟通变得更加自然与高效。声音转文本&#xff08;Speech-to-Text, STT&…