解锁ArrayBlockingQueue奥秘:深入源码的精彩之旅

1.简介

ArrayBlockingQueueBlockingQueue 接口的一个实现类,它基于数组实现了一个有界阻塞队列。创建 ArrayBlockingQueue 实例时需要指定队列的容量,队列的大小是固定的,无法动态增长

主要特点包括:

  • 有界性ArrayBlockingQueue容量是固定的,在创建队列时需要指定容量大小,一旦队列达到最大容量,再尝试向队列中添加元素将会导致阻塞,直到队列中有空间。

  • 线程安全性: 提供了线程安全的队列操作,多个线程可以安全地在队列中进行添加和移除元素的操作,无需额外的同步措施。

  • FIFO 排序:基于数组实现,采用先进先出(FIFO)的顺序,保证了队列中元素的顺序性

  • 阻塞操作:当尝试向队列中添加元素时,如果队列已满生产者线程将会被阻塞,直到队列中有空间可用;当尝试从队列中取出元素时,如果队列为空消费者线程将会被阻塞,直到队列中有新的元素可用。

  • 性能稳定:基于数组实现的,在添加和移除元素时具有稳定的性能表现,与队列大小无关。

2.重要的方法

BlockingQueue 具有 4 组不同的方法用于插入移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:

抛异常特定值阻塞超时
插入add(o)offer(o)put(o)offer(o, timeout, timeunit)
插入remove()poll()take()poll(timeout, timeunit)
检查element()peek()

四组不同的行为方式解释:

  • 抛异常: 如果试图的操作无法立即执行,抛一个异常

  • 特定值: 如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。

  • 阻塞: 如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。

  • 超时: 如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

无法向一个 ArrayBlockingQueue 中插入 null。如果你试图插入nullArrayBlockingQueue 将会抛出一个 NullPointerException

3.重要属性

   final Object[] items;int takeIndex;int putIndex;int count;final ReentrantLock lock;private final Condition notEmpty;private final Condition notFull;
  1. items 数组: 用于存储队列元素的数组。

  2. count: 用于记录当前队列中的元素数量

  3. takeIndexputIndex: 分别表示队列头部尾部元素在数组中的索引位置。

  4. lock: 使用ReentrantLock来实现对队列操作的线程安全控制

  5. notEmptynotFull: 条件变量,用于实现队列非空队列未满的等待通知机制

这些属性在ArrayBlockingQueue 内部使用,可以帮助实现队列的基本功能和线程安全访问。

4.构造方法

构造方法如下所示:

  public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull =  lock.newCondition();}
  1. 首先,构造方法接受两个参数:capacity 表示队列的容量大小,fair 表示是否使用公平性策略

  2. 在方法中首先对 capacity 进行检查,如果小于等于 0,则抛出 IllegalArgumentException 异常。

  3. 然后,创建一个 Object 类型的数组 items 作为队列的存储结构,其大小为 capacity,即指定了队列的容量。

  4. 创建一个 ReentrantLock 对象 lock,用于在多线程环境下对队列进行加锁操作。fair 参数用于决定是否使用公平性策略,如果为 true,则表示使用公平性策略,否则为非公平性策略。

  5. 最后,创建两个 Condition 对象 notEmptynotFull,它们分别用于表示队列非空非满的条件,通过它们可以实现线程的等待唤醒操作。

5.添加元素方法

5.1 offer(e)方法

offer(e)方法会在加锁的情况下尝试向队列中添加元素,如果队列已满则返回false,否则将元素插入队列并返回true

public boolean offer(E e) {if (e == null) throw new NullPointerException();final ReentrantLock lock = this.lock;lock.lock();try {if (count == items.length)return false;else {enqueue(e);return true;}} finally {lock.unlock();}
}
  1. 首先,对传入的元素e进行空指针检查,如果为空则抛出NullPointerException

  2. 获取队列的ReentrantLock对象lock,并对其进行加锁操作。

  3. 在加锁的情况下,判断队列是否已满,如果已满则返回false;如果未满,调用enqueue(e)方法将元素e插入队列,并返回true

  4. 最后,无论是否成功添加元素,最终都会释放锁

5.1.1 enqueue(e)方法

enqueue(e)插入元素,并通过更新putIndexcount来维护队列的状态,同时通过notEmpty.signal()唤醒可能阻塞在队列非空条件上的消费者线程。

/*** Inserts element at current put position, advances, and signals.* Call only when holding lock.*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)putIndex = 0;
count++;
notEmpty.signal();
}
  1. 首先获取ArrayBlockingQueue中的items数组,items数组用于存放队列中的元素。

  2. items[putIndex] = x;: 将元素x插入到items数组的putIndex位置上,putIndex表示当前要插入元素的位置

  3. if (++putIndex == items.length) putIndex = 0;: 这一行代码用于更新putIndex的值,如果putIndex超出了数组长度,则将其置为0,实现循环利用数组空间的目的。

  4. count++;: 增加队列的元素数量计数器count

  5. notEmpty.signal();: 发送信号通知处于等待状态的消费者线程,队列中已经有元素可以消费了。

5.2 offer(o, timeout, timeunit)方法源码如下:

offer(o, timeout, timeunit)方法会在加锁的情况下尝试向队列中添加元素,在指定的超时时间内等待队列有空间可用,如果成功添加则返回true,超时未成功则返回false,如果被中断则抛出InterruptedException异常。

public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {checkNotNull(e);long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length) {if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}enqueue(e);return true;} finally {lock.unlock();}
}
  1. 首先,对传入的元素e进行空指针检查,如果为空则抛出NullPointerException

  2. timeout转换为纳秒单位,并获取队列的ReentrantLock对象lock,使用lockInterruptibly方法加锁,支持响应中断

  3. 加锁的情况下,如果队列已满,则会进入循环等待,同时根据剩余的超时时间不断进行等待,直到超时或者队列有空间可用。

  4. 如果在等待过程中成功添加元素,则返回true,否则如果超时未成功添加元素则返回false

  5. 最后,无论是否成功添加元素,最终都会释放锁

5.3 add(e)方法源码如下:

add(e)方法是调用父类AbstractQueueadd(e)方法:

public boolean add(E e) {
return super.add(e);
}
5.3.1 AbstractQueue的add(e)方法,源码如下

add(e)方法会首先尝试向队列中添加元素,如果成功则返回true,如果队列已满抛出异常

public boolean add(E e) {if (offer(e)) {return true;} else {throw new IllegalStateException("Queue full");}
}
  1. 首先,调用了offer(e)方法尝试向队列中添加元素

  2. 如果成功添加元素,则返回true

  3. 如果队列已满offer(e)方法会返回false,此时会抛出IllegalStateException异常,提示队列已满

5.4 put(e)方法

put(e)方法会在加锁的情况下尝试向队列中添加元素,如果队列已满则会阻塞等待直到有空间可以插入元素,如果被中断则会抛出InterruptedException异常。

public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}
}
  1. 首先,对传入的元素e进行空指针检查,如果为空则抛出NullPointerException

  2. 获取队列的ReentrantLock对象lock,并使用lockInterruptibly方法加锁,支持响应中断

  3. 加锁的情况下,如果队列已满,则使用notFull条件变量等待直到队列有空间可以插入元素。

  4. 一旦有空间可以插入元素,调用insert(e)方法将元素e插入队列。

  5. 最后,无论是否成功添加元素,最终都会释放锁

6.移除元素

6.1 poll()方法

poll()方法用于从队列中取出并移除头部的元素,如果队列为空,则返回null

public E poll() {final ReentrantLock lock = this.lock;lock.lock();try {return (count == 0) ? null : dequeue();} finally {lock.unlock();}
}
  1. 首先获取队列中的锁对象lock,并尝试获取锁

  2. 判断队列元素数量count是否为0,如果为0则表示队列为空,直接返回null

  3. 如果队列不为空,则调用dequeue()方法来取出并返回队列头部的元素

  4. 最后释放锁并返回取出的元素。

6.1.1 dequeue()方法

dequeue()主要完成了从队列中取出元素的操作,并通过更新takeIndexcount来维护队列的状态,同时通过notFull.signal()来唤醒可能阻塞在队列非满条件上的生产者线程。

/*** Extracts element at current take position, advances, and signals.* Call only when holding lock.*/
private E dequeue() {// assert lock.getHoldCount() == 1;// assert items[takeIndex] != null;final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();notFull.signal();return x;
}
  1. final Object[] items = this.items;: 首先获取items数组,该数组用于存放队列中的元素。

  2. E x = (E) items[takeIndex];: 将takeIndex位置上的元素强制转换为泛型类型E,并赋值给变量x,表示要取出的元素。 3. items[takeIndex] = null;: 将takeIndex位置上的元素置为null,表示该位置上的元素已经被取出。

  3. if (++takeIndex == items.length) takeIndex = 0;: 更新takeIndex的值,如果takeIndex超出了数组长度,则将其置为0,实现循环利用数组空间的目的。

  4. count--;: 减少队列的元素数量计数器count。

  5. if (itrs != null) itrs.elementDequeued();: 如果存在迭代器iterator,则调用elementDequeued()方法通知迭代器,表示有元素被取出。

  6. notFull.signal();: 发送信号通知处于等待状态的生产者线程,队列中已经有空间可以生产新元素了。

  7. return x;: 返回被取出的元素。

6.2 l(long timeout, TimeUnit unit)方法

poll(long timeout, TimeUnit unit)方法用于从队列中取出并移除头部的元素,如果队列为空,则在指定的时间范围等待元素可用。当超时时间到达后仍然没有可用元素,则返回null

public E poll(long timeout, TimeUnit unit) throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0) {if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}return dequeue();} finally {lock.unlock();}}
  1. 将传入的timeout参数转换纳秒单位。

  2. 获取队列中的锁对象lock,并尝试获取锁(支持中断)。

  3. 当队列为空时,进入循环等待状态。如果nanos小于等于0,表示超时时间已到,直接返回null

  4. 使用notEmpty.awaitNanos(nanos)来等待非空条件满足,并根据等待时间的剩余纳秒数更新nanos值。

  5. 当队列不为空时,调用dequeue()方法来取出并返回队列头部的元素

  6. 最终释放锁并返回取出的元素。

6.3 remove()方法

remove()方法用于移除并返回队列的头部元素。如果队列为空,则抛出NoSuchElementException异常。

public E remove() {E x = poll();if (x != null)return x;elsethrow new NoSuchElementException();
}
  1. 调用队列的poll()方法来尝试取出并返回队列的头部元素。

  2. 如果poll()方法返回的元素不为null,表示成功取出了一个元素,则直接返回该元素。

  3. 如果poll()方法返回的元素为null,表示队列为空,此时抛出NoSuchElementException异常。

6.4 take()方法

take()方法用于从队列中取出并移除头部的元素。如果队列为空,该方法将阻塞直到队列中有可用元素为止。

 public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}
  1. 获取队列中的锁对象lock,并尝试获取锁(支持中断)。

  2. 当队列为空时,进入循环等待状态,调用notEmpty.await()来等待非空条件满足,即等待队列中有元素可用。

  3. 当队列不为空时,调用dequeue()方法来取出并返回队列头部的元素。

  4. 最终释放锁并返回取出的元素。

7.检查元素

7.1 peek

在调用peek()时,会返回队列头部的元素不将其从队列中移除。如果队列为空,则返回null。

public E peek() {final ReentrantLock lock = this.lock;lock.lock();try {return itemAt(takeIndex); // null when queue is empty} finally {lock.unlock();}
}
  1. 获取队列中的锁对象lock,并尝试获取锁

  2. 调用itemAt(int i)获取索引为takeIndex的元素。

  3. 最终释放锁

7.2 itemAt

返回items中索引为i的元素

final E itemAt(int i) {
return (E) items[i];
}
7.3 element

element()方法用于获取但不移除队列的头部元素。如果队列为空,则抛出NoSuchElementException异常。

public E element() {E x = peek();if (x != null)return x;elsethrow new NoSuchElementException();
}
  1. 调用队列的peek()方法来获取但不移除队列的头部元素。

  2. 如果peek()方法返回的元素不为null,表示成功获取了一个元素,则直接返回该元素。

  3. 如果peek()方法返回的元素为null,表示队列为空,此时抛出NoSuchElementException异常。

 关注公众号:小黄学编程 回复:架构师 获取小黄收集的架构师资料

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

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

相关文章

STM32——hal_SPI_(介绍)

SPI&#xff08;串行外围设备接口&#xff09;是一种高速的、全双工、同步的通信协议&#xff0c;通常用于短距离通信&#xff0c;尤其是在嵌入式系统中与各种外围设备进行通信。SPI接口由摩托罗拉公司推出&#xff0c;由于其简单和灵活的特性&#xff0c;它被广泛用于多种应用…

2023年计算机图形学课程知识总结

去年就该写的&#xff0c;但是去年这个时候太忙了。 就写来自己看看。留个记录留个念 文章目录 1. 图形&#xff0c;图像的定义2. 点阵、矢量3. 走样&#xff0c;反走样4. 字符裁剪精度&#xff08;1&#xff09; 串精度&#xff08;2&#xff09; 字符精度&#xff08;3&…

SpringBoot打war包并配置外部Tomcat运行

简介 由于其他原因&#xff0c;我们需要使用SpringBoot打成war包放在外部的Tomcat中运行,本文就以一个案例来说明从SpringBoot打war包到Tomcat配置并运行的全流程经过 环境 SpringBoot 2.6.15 Tomcat 8.5.100 JDK 1.8.0_281 Windows 正文 一、SpringBoot配置打war包 第一步&a…

J-Lin烧录

1、J-linK介绍 J-Link是由德国SEGGER公司推出的&#xff0c;主要用于支持仿真ARM内核芯片的JTAG仿真器。它支持JTAG和SWD两种模式&#xff0c;可以配合多种集成开发环境&#xff08;如IAR EWAR, ADS, KEIL, WINARM, RealView等&#xff09;使用&#xff0c;支持ARM7/ARM9/ARM…

odoo16 档案管理

档案管理&#xff0c;odoo15升级至odoo16完善 电子档案管理是指将传统纸质档案数字化&#xff0c;以便更加方便、快捷、安全地进行档案管理。电子档案管理系统可以对档案进行数字化、存储、检索、共享、传递和销毁等操作&#xff0c;从而提高了档案管理的效率和准确性&#xf…

使用cesiumLab使shp转为3dtlies

过程不做赘述&#xff0c;网上大把&#xff0c;说下注意事项。 1. 存储3DTiles 选项 若是打开则输出的文件为glb格式文件,因为glb文件好储存易传输跨平台。cesium可以使用但无法处理&#xff0c;例如改变颜色&#xff0c;改着色器等。若是不打开则输出的文件为bm3d格式文件,此…

Bond网卡

一、Bond网卡 1.1 Bond网卡概述 Bond网卡是指使用 Linux 系统中的 Bonding 技术创建的虚拟网络接口。 Bonding 技术允许将多个物理网卡&#xff08;也称为接口或端口&#xff09;绑定在一起&#xff0c;形成一个虚拟的网络接口&#xff0c;以增加网络带宽、提高网络容错性和…

表达式求值中的“整型提升”概念

一.基本原理和概念 如&#xff1a;代码 char a&#xff0c;b&#xff0c;c &#xff1b; a b c &#xff1b; 该代码在计算的时候就会先将 b 和 c 提升为 int 类型进行加法后&#xff0c;再将数据进行截断存放在内存存放变量 a 的空间中。 &#xff08;1&#xff09;提升和截…

眼底照 + OCT图 + 精神状态 ,预测阿尔兹海默症

眼底照片和OCT图像&#xff0c;预测阿尔兹海默症 数据多模态网络模型集成可视化分析 论文&#xff1a;https://www.ophthalmologyretina.org/action/showPdf?piiS2468-6530%2824%2900045-9 目前&#xff0c;认知障碍的诊断依赖于血清和蛋白质生物标志物的检测、脑脊液检查和正…

【教程】WordPress主题子比主题 添加私密评论功能

教程如下 打开子比主题的 functions.php 文件,在最后一个 ?> 的前面添加以下代码: //私密评论 function liao_private_message_hook( $comment_content , $comment){$comment_ID = $comment->comment_ID; $parent_ID = $comment->comment_parent; $parent_emai…

[SaaS] AI+数据,tiktok选品,找达人,看广告数据

TK观察专访丨前阿里“鲁班”创始人用AIGC赋能TikTok获千万融资用AI数据做TikTokhttps://mp.weixin.qq.com/s/xp5UM3ROo48DK4jS9UBMuQ主要还是爬虫做数据的。 商家做内容&#xff1a;1.找达人拍内容&#xff0c;2.商家自己做原生自制内容&#xff0c;3.广告内容。 短视频&…

南京观海微电子----焊机用DC-DC 24V 升压电路分析

焊机用DC-DC 24V 升压电路分析 辅电升压电路关键元件有&#xff1a;UC3843、电感、MOS功率管、整流二极管等组成。其核心是UC3843。UC3843是脉宽调制IC&#xff0c;工作频率可达500kHz&#xff0c;组成电路引脚少、外围元件简单&#xff0c;启动电流仅需1mA&#xff0c;开启电压…

BC C language

题目汇总 No.1 打印有规律的字符(牛牛的字符菱形) 代码展示 #include<stdio.h> int main() {char ch=0;scanf("%c"

Meta Llama 3 残差结构

Meta Llama 3 残差结构 flyfish 在Transformer架构中&#xff0c;残差结构&#xff08;Residual Connections&#xff09;是一个关键组件&#xff0c;它在模型的性能和训练稳定性上起到了重要作用。残差结构最早由He et al.在ResNet中提出&#xff0c;并被广泛应用于各种深度…

ArUco与AprilTag 标签

一、简介 在许多计算机视觉应用程序中&#xff0c;姿势估计非常重要&#xff1a;机器人导航&#xff0c;增强现实等等。 该过程基于发现真实环境中的点与其2d图像投影之间的对应关系。 这通常是一个困难的步骤&#xff0c;因此通常使用合成或基准标记来简化操作。 最受流行的…

Day13 配置AutoMapper关系映射

在上一节 中,无论ToDoController 控制器,或 IToDoService 服务的接口中,方法的实参必须是传实体类。但在实际开发过程中,这样是不允许的。标准且规范的做法是,定义一个数据传输层,即DTO层。 DTO(Data Transfer Objects)数据传输对象,它是一种设计模式,主要用于在不同…

【机器学习-09】 | Scikit-Learn工具包进阶指南:Scikit-Learn工具包之高斯混合sklearn.mixture模块研究

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

【全网唯一】触摸精灵iOS版纯离线本地文字识别插件

目的 触摸精灵iOS是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务&#xff0c;节省大量人工操作的时间。但触摸精灵的图色功能比较单一&#xff0c;无法识别屏幕上的图像&#xff0c;根据图像的变化自动执行相应的操作。本篇文章主要…

【Python绘画】画正方形简笔画

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、代码示例三、知识点梳理四、总结 一、前言 本文介绍如何使用Python的海龟画图工具turtle&#…

Stable Diffusion 临时文件夹设定

即使将Easy Diffusion或Stable Diffusion安装在C盘之外的某个地方&#xff0c;如果持续使用Stable Diffusion一段时间后&#xff0c;仍然会发现C盘空间在快速变小。这是因为有很多自动下载的文件还是保存在C盘。为了解决这个问题&#xff0c;我们需要做以下临时文件夹设定。 H…