滚雪球学Java(71):深入剖析Java中的ArrayBlockingQueue底层实现与源码分析

在这里插入图片描述

  咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~

在这里插入图片描述


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

文章目录

  • 前言
  • 摘要
  • ArrayBlockingQueue
    • 概述
    • 源代码解析:
      • add方法:
      • offer方法:
      • put方法:
      • take方法:
      • poll方法:
      • remove方法:
    • 应用场景案例:
    • 优缺点分析:
      • 优点:
      • 缺点:
    • 类代码方法介绍:
    • 测试用例
      • 测试代码演示
      • 测试结果
      • 测试代码分析
    • 小结
  • 总结
    • 附录源码
  • ☀️建议/推荐你
  • 📣关于我

前言

  在Java开发中,队列是一种常见的数据结构,而其中的ArrayBlockingQueue是一个经典且实用的队列实现。它采用数组作为底层实现,在多线程并发访问下表现出较好的稳定性和高效性。本文将深入剖析Java中的ArrayBlockingQueue底层实现与源码分析。

摘要

  本文通过对ArrayBlockingQueue的源代码解析,分析其底层实现原理,探讨其在多线程并发处理场景下的应用场景,并对其优缺点做出评估。同时,本文还对类中的各个方法进行了介绍,并给出了测试用例和总结。

ArrayBlockingQueue

概述

  ArrayBlockingQueue是Java中的一个队列实现类,它使用一个定长的数组作为底层数据结构,并支持多线程并发访问。当队列为空时,从队列中获取元素的操作将会被阻塞,直到队列中出现新的元素;当队列满时,向队列插入元素的操作也会被阻塞,直到之前的元素被取出或者队列被清空。ArrayBlockingQueue可以作为生产者-消费者模式的一种经典实现方案,在多线程并发处理场景下具有广泛的应用。

源代码解析:

ArrayBlockingQueue类定义了如下的构造函数:

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();
}

  其中,capacity表示队列的容量,fair表示是否采用公平的锁机制来进行多线程访问。在构造函数中,我们可以看到该类使用了一个Object类型的数组来作为队列的底层数据结构,同时还创建了两个Condition对象来分别表示队列为空和队列已满的情况。

  该类中主要的方法包括add、offer、put、take、poll、remove等,这些方法用于向队列中添加和删除元素。下面我们就对其中的一些方法进行解析。

  如下是部分源码截图:

在这里插入图片描述

add方法:

public boolean add(E e) {if (offer(e))return true;elsethrow new IllegalStateException("Queue full");
}

  该方法先调用了offer方法来将元素添加到队列中,并在offer方法返回false时抛出了一个IllegalStateException异常。

拓展:

  这是一个Java中的队列数据结构的添加元素方法的实现。

代码中首先调用了队列中的offer方法,该方法用于将元素插入到队列的尾部,并返回插入是否成功的结果(true表示插入成功,false表示插入失败)。

如果插入成功,add方法直接返回true表示插入成功;如果插入失败,add方法会抛出一个IllegalStateException异常,表示队列已满,无法继续插入元素。

因为这个队列应该有一个容量限制,所以当队列已经满了,就不能再放入元素了,此时抛出 IllegalStateException 异常,提示调用者队列已满。

在这里插入图片描述

offer方法:

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 {insert(e);return true;}} finally {lock.unlock();}
}

  该方法首先判断参数e是否为null,然后获取类中的ReentrantLock对象,并使用该对象进行加锁操作。在获取锁后,该方法会判断队列是否已满,如果已满则返回false,否则调用insert方法将元素插入到队列中,然后返回true。最后,该方法释放锁并返回结果。

拓展:

  这是一个队列的 offer 方法实现,用于向队列中添加元素。它的参数是要添加的元素 e,返回值是一个 boolean 类型,表示添加是否成功。首先,它会判断要添加的元素是否为 null,如果是则抛出 NullPointerException 异常。然后获取队列的锁,使用的是 ReentrantLock,确保线程安全。接下来,它会检查队列的容量是否已满,如果已满则无法添加,返回 false,否则就调用 insert 方法将元素添加到队列中,并返回 true 表示添加成功。最后,释放锁。

在这里插入图片描述

put方法:

public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length) {notFull.await();}insert(e);} finally {lock.unlock();}
}

  该方法和offer方法类似,不同之处在于当队列已满时,该方法会一直等待队列中出现空闲空间。在wait之前,该方法获取lock对象的锁,在wait之后释放锁。

拓展:

  这是一个生产者向有界队列中添加元素的方法。如果队列已满,方法会阻塞直到队列不满为止。

具体解释如下:

  1. 如果待插入元素 e 为 null,方法会抛出空指针异常。

  2. 获取当前队列的可重入锁,使用 lockInterruptibly() 方法可以在等待锁的过程中响应中断。

  3. 在锁的保护下,当队列已满时,使用 notFull.await() 方法将当前线程阻塞,等待 notFull 条件变量的通知,表示队列非满。

  4. 当 notFull 条件变量被通知时,将元素 e 插入到队列中,使用 insert(e) 方法完成插入操作。

  5. 最后,释放锁。

  总体来说,这个方法在多线程环境下保证了有界队列的线程安全,同时也保证了生产者线程在队列已满时的阻塞加入操作。

在这里插入图片描述

take方法:

public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0) {notEmpty.await();}return extract();} finally {lock.unlock();}
}

  该方法和put方法类似,不同之处在于take方法会在队列为空时等待队列中出现新的元素。在wait之前,该方法获取lock对象的锁,在wait之后释放锁。

拓展:

  这段代码实现了阻塞队列中的元素删除操作,使用了可中断锁来保证线程安全。

具体操作流程如下:

  1. 获取可重入锁 lock。

  2. 尝试获取锁,如果获取不到则线程被阻塞。

  3. 如果队列为空,则调用 notEmpty.await() 方法使线程等待。

  4. 如果队列不为空,则调用 extract() 方法删除队列中的元素并返回。

  5. 释放锁。

  值得注意的是,await() 方法会让当前线程进入一个等待状态,直到被唤醒或者中断。而 extract() 方法则是队列中元素删除的具体实现方法。

在这里插入图片描述

poll方法:

public E poll() {final ReentrantLock lock = this.lock;lock.lock();try {if (count == 0)return null;E x = extract();return x;} finally {lock.unlock();}
}

  该方法首先获取lock对象的锁,然后判断队列是否为空。如果队列为空,则返回null,否则调用extract方法从队列中提取元素,然后释放锁并返回该元素。】

拓展:

  这段代码实现了BlockingQueue接口中的offer方法,用于向队列中插入元素。具体分析如下:

  1. 首先进行参数检查,如果插入的元素为null,则抛出NullPointerException异常。

  2. 获取队列的锁,使用ReentrantLock实现。如果锁获取失败则会一直等待。

  3. 判断队列是否已满,如果已满则直接返回false表示插入失败;如果未满,则调用insert方法进行实际的插入操作,并返回true表示插入成功。

  4. 最后释放队列的锁。

  总体来说,这段代码实现了一个线程安全的队列,保证了在多线程环境下插入元素的正确性和一致性。同时,通过使用ReentrantLock实现锁的获取和释放,相比于synchronized关键字,具有更高的灵活性和可控性。

在这里插入图片描述

remove方法:

public boolean remove(Object o) {if (o == null) return false;final Object[] items = this.items;final ReentrantLock lock = this.lock;lock.lock();try {for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) {if (o.equals(items[i])) {removeAt(i);return true;}}return false;} finally {lock.unlock();}
}

  该方法首先判断参数o是否为null,然后获取items和lock对象。在获取锁后,该方法会遍历队列中的元素,查找和参数o相等的元素,并调用removeAt方法将其从队列中删除。最后释放锁并返回结果。

拓展:

  这段代码实现了ConcurrentLinkedQueue的remove方法,用于从队列中删除指定的元素。

  首先,判断待删除元素是否为null,如果是则直接返回false,因为队列中不允许添加null元素。然后获取队列的元素数组和锁对象,通过锁保证并发操作的线程安全性。接下来,循环遍历队列中的元素,从takeIndex开始遍历,取模运算保证了下标的循环使用。k表示队列中元素的个数,每找到一个匹配的元素,就使用removeAt方法将其删除,然后返回true。如果遍历整个队列都没有找到匹配的元素,说明该元素不在队列中,返回false。

  最后,解锁锁对象,结束方法。

在这里插入图片描述

应用场景案例:

  ArrayBlockingQueue可以应用于生产者-消费者模式等多线程并发处理场景。例如,在一个Web应用中,我们需要从数据库中读取大量数据并将其渲染为Web页面。由于数据库读取和页面渲染是两个耗时的操作,我们可以使用ArrayBlockingQueue来实现生产者-消费者模式,其中生产者可以将数据库中读取的数据放入队列中,而消费者则从队列中获取这些数据并进行渲染。

优缺点分析:

优点:

  1. 线程安全:ArrayBlockingQueue是线程安全的,多个线程可以同时访问队列中的元素而不会发生冲突。

  2. 高效性:ArrayBlockingQueue使用数组作为底层数据结构,具有较高的效率。

  3. 容量限制:ArrayBlockingQueue的容量是有限制的,可以避免队列过大导致内存溢出等问题。

缺点:

  1. 容量限制:由于队列的容量是有限制的,因此可能会出现无法存储更多元素的情况。

  2. 吞吐量:在高并发情况下,ArrayBlockingQueue的吞吐量可能会受到一定的限制。

类代码方法介绍:

  ArrayBlockingQueue类中的各个方法在上面已经进行了介绍,在此不再赘述。

测试用例

下面是一个简单的测试用例:

测试代码演示

package com.demo.javase.day71;import java.util.concurrent.ArrayBlockingQueue;/*** @Author bug菌* @Date 2023-11-06 16:15*/
public class ArrayBlockingQueueTest {public static void main(String[] args) throws InterruptedException {ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);new Thread(() -> {for (int i = 1; i <= 10; i++) {try {queue.put(i);} catch (InterruptedException e) {e.printStackTrace();}}}).start();Thread.sleep(1000);new Thread(() -> {for (int i = 1; i <= 10; i++) {try {int x = queue.take();System.out.println(x);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

测试结果

  根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

  上述测试用例中,我们创建了一个容量为10的ArrayBlockingQueue队列,并创建了两个线程,一个线程用于向队列中添加元素,另一个线程用于从队列中取出元素。在执行过程中,我们使用sleep方法让主线程阻塞1秒钟,以防止线程执行过程中队列未被填满或清空。测试结果如下所示:

1
2
3
4
5
6
7
8
9
10

  可以看到,程序能够正常地向队列中添加元素并从队列中取出元素。

实际执行结果如下:

在这里插入图片描述

测试代码分析

  根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。
  这段代码实现了使用ArrayBlockingQueue作为线程之间共享的队列。具体来说,程序启动两个线程,一个线程将1-10的整数依次放入队列中,另一个线程从队列中取出10个整数并打印输出。

在这个过程中,因为ArrayBlockingQueue具有阻塞特性,即当队列已满时,向队列中添加元素的线程会被阻塞,直到队列中有空余位置;当队列为空时,从队列中取出元素的线程会被阻塞,直到队列中有元素可取。因此,该程序可以保证线程间的同步,避免了多线程并发访问共享变量时可能出现的竞态条件问题。

小结

  本文对Java中的ArrayBlockingQueue队列实现进行了深入的分析,包括其底层数据结构、构造函数、主要方法以及应用场景等方面。ArrayBlockingQueue作为一个经典的队列实现,虽然存在一定的缺点,但在多线程并发处理场景下具有广泛的应用前景。在实际开发中,我们可以根据具体情况选择合适的队列实现,并结合线程池等技术来优化程序性能。

总结

  本文深入剖析了Java中的ArrayBlockingQueue队列实现,探讨了其底层实现原理以及在多线程并发处理场景下的应用场景。同时对类中的各个方法进行了介绍,并给出了测试用例和优缺点评估。ArrayBlockingQueue作为一个经典的队列实现,具有线程安全和高效性等优点,但由于容量限制可能出现无法存储更多元素的情况,吞吐量也可能受到一定限制。在实际开发中,我们可以根据具体情况选择合适的队列实现,并结合线程池等技术来优化程序性能。

  …
  好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

📣关于我

  我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。


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

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

相关文章

SpringMVC 学习(一)之 SpringMVC 介绍

目录 1 MVC 介绍 2 SpringMVC 介绍 2.1 SpringMVC 特点 2.2 SpringMVC 的核心组件 2.3 SpringMVC 执行流程 3 参考文档 1 MVC 介绍 MVC (Model View Controller) 是一种设计思想&#xff0c;它将应用程序分为三大组件&#xff1a;模型 (Model)、视图 (View)、控制器 (Con…

在openEuler中通过KVM可视化安装华为FusionCompute的VRM节点

一、说明 本文是华为FusionCompute云平台配置的延续&#xff0c;是在CNA&#xff08;ComputingNode Agent&#xff0c;计算节点代理&#xff09;主机安装配置完成后&#xff0c;详细安装VRM&#xff08;Virtual Resource Manager&#xff0c;虚拟资源管理器&#xff09;节点的…

「JavaSE」String类3:字符串常量池

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;快来卷Java啦 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 字符串常量池 &#x1f349;常量池&#x1f349;字符串常量池&#x1f349;intern 方法 &#x1f349;常量池 在Java程序中&…

微服务-Alibaba微服务nacos实战

1. Nacos配置中心 1.1 微服务为什么需要配置中心 在微服务架构中&#xff0c;当系统从一个单体应用&#xff0c;被拆分成分布式系统上一个个服务节点后&#xff0c;配置文件也必须跟着迁移&#xff08;分割&#xff09;&#xff0c;这样配置就分散了&#xff0c;不仅如此&…

*MYSQL--索引--内部原理

MYSQL的索引根据功能,主要有三大类型: 1.HASH索引 2.二叉树 3.BTREE索引 一:HASH索引 1.内部原理: 在设置了某列为索引列之后,并且开始或者将要在相应索引列创建数据的时候,系统通过某种算法 F(X) 自动计算出来一个十六进制的哈希值,这个哈希值能够对应相应的字段值 所以…

数据之巅:揭秘企业数据分析师如何成为企业的决策智囊

引言 在数字化浪潮中&#xff0c;企业数据分析师已成为企业决策的重要支撑。他们如同探险家&#xff0c;在数据的丛林中寻找着能够指引企业前行的宝贵信息。本文将深入剖析企业数据分析师的角色、挑战与成就&#xff0c;带你领略这个充满智慧与激情的职业风采。 一、从数字到智…

28-k8s集群中-StatefulSets控制器(进阶知识)

一、statefullsets控制器概述 1&#xff0c;举例 假如&#xff0c;我们有一个deployment资源&#xff0c;创建了3个nginx的副本&#xff0c;对于nginx来讲&#xff0c;它是不区分启动或者关闭的先后顺序的&#xff0c;也就是“没有特殊状态”的一个服务&#xff0c;也成“无状…

十四届蓝桥杯国赛考试计算思维 U10 组真题和答案

答案在底部 第一题 恐龙乐园的规划图中有 n 个小岛,m 座小桥,每座桥连接两个小岛。 下图是 n=5,m=8 的一个例子: 希希发现,如果拆除一些桥,仍然能使任何两个小岛都互通。最多可以拆除( )座 桥。 A. n-m B. m-n C. m-m-1 D. m-n+1 第二题

探索设计模式的魅力:状态模式揭秘-如何优雅地处理复杂状态转换

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;并且坚持默默的做事。 探索设计模式的魅力&#xff1a;状态模式揭秘-如何优雅地处理复杂状态转换 文章目录 一、案例…

VUE3使用three.js显示3D模型

使用3D模型作为自动化系统的界面已经成为潮流。尽管在AutomationML 中推荐COLLADA作为 3D 模型的标准。但是COLLADA的开发者khronos 集团却已经开发了COLLADA的后继者glTF 。glTF 2.0已经成为国际标准ISO/IEC 12113:2022。 Khronos声称-‘glTF是3D里的JPEG’。目前看来glTF 更加…

问题慢慢解决-通过android emulator调试android kernel-内核条件断点遇到的问题和临时解决方案

起因 在摸索到这个方案之后&#xff0c;mac m1调试aarch64 android kernel最终方案&#xff0c;就准备调试内核了&#xff0c;预备下断点的地方是 b binder_poll b ep_ptable_queue_proc b remove_wait_queue但是由于是android系统&#xff0c;上面三个函数会被频繁的触发&am…

ElementUI table表格组件实现双击编辑单元格失去焦点还原,支持多单元格

在使用ElementUI table表格组件时有时需要双击单元格显示编辑状态&#xff0c;失去焦点时还原表格显示。 实现思路&#xff1a; 在数据中增加isFocus:false.控制是否显示在table中用cell-dblclick双击方法 先看效果&#xff1a; 上源码&#xff1a;在表格模板中用scope.row…

springboot集成mqtt

文章目录 前言一、MQTT是什么&#xff1f;二、继承步骤1.安装MQTT2.创建项目&#xff0c;引入依赖3. 对应步骤2的代码3 测试 总结mqtt 启动后访问地址 前言 随着物联网的火热,MQTT的应用逐渐增多 曾经也有幸使用过mqtt,今天正好总结下MQTT的使用; 一、MQTT是什么&#xff1f;…

从扩散模型基础到DIT

Diffusion model 扩散模型如何工作&#xff1f; 输入随机噪声和文本内容&#xff0c;通过多次预测并去除图片中的噪声后&#xff0c;最终生成清晰的图像。 以上左边这张图&#xff0c;刚开始是随机噪声&#xff0c;999为时间序列。 为什么不直接预测下一张图片呢&#xff1f;…

springboot+vue的宠物咖啡馆平台(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

【深度学习笔记】3_4 逻辑回归之softmax-regression

3.4 softmax回归 Softmax回归&#xff08;Softmax Regression&#xff09;&#xff0c;也称为多类逻辑回归&#xff08;Multinomial Logistic Regression&#xff09;&#xff0c;是一种用于多分类问题的分类算法。虽然名字里面带回归&#xff0c;实际上是分类。 前几节介绍的…

Rust之构建命令行程序(四):用TDD(测试-驱动-开发)模式来开发库的功能

开发环境 Windows 11Rust 1.75.0 VS Code 1.86.2 项目工程 这次创建了新的工程minigrep. 用测试-驱动模式来开发库的功能 既然我们已经将逻辑提取到src/lib.rs中&#xff0c;并将参数收集和错误处理留在src/main.rs中&#xff0c;那么为代码的核心功能编写测试就容易多了。我…

【MATLAB源码-第146期】基于matlab的信源编码仿真GUI,对比霍夫曼编码,算术编码和LZ编码。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 霍夫曼编码、算术编码和LZ编码是三种广泛应用于数据压缩领域的编码技术。它们各自拥有独特的设计哲学、实现方式和适用场景&#xff0c;因此在压缩效率、编解码速度和内存使用等方面表现出不同的特点。接下来详细描述这三种编…

【大厂AI课学习笔记】【2.2机器学习开发任务实例】(7)特征构造

特征分析之后&#xff0c;就是特征构造。 特征构造第一步 特征构造往往要进行数据的归一化。 在本案例中&#xff0c;我们将所有的数据&#xff0c;将所有特征区间调整为0~1之间。 如上图。 那么&#xff0c;为什么要进行归一化&#xff0c;又如何将数据&#xff0c;调整为…

QtCreator“设计”按钮灰色无法点击,如何解决

Mac中安装QML Designer插件&#xff1a; 首选项-> 关于插件 -> 勾选QT Quick下的QML Designer 点击确定安装插件&#xff0c;重启Qt Creator后生效