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

深入探索Java并发编程:ArrayBlockingQueue详解

在这里插入图片描述

码到三十五 : 个人主页

心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 !


在Java的并发编程世界中,java.util.concurrent包为我们提供了多种用于线程间安全通信的数据结构,其中ArrayBlockingQueue是一个备受瞩目的有界阻塞队列。本文将全面深入地介绍ArrayBlockingQueue的内部机制、使用场景以及最佳实践。

目录

      • 一、ArrayBlockingQueue概述
      • 二、内部机制
        • 2.1. 数据结构
        • 2.2. 锁和条件变量
        • 2.3. 入队和出队操作
      • 三、使用场景
      • 四、最佳实践
      • 五、ArrayBlockingQueue实现生产者-消费者
      • 六、总结

一、ArrayBlockingQueue概述

ArrayBlockingQueue是一个基于数组的有界阻塞队列。它在创建时需要指定队列的大小,并且这个大小在之后是不能改变的。队列中的元素按照FIFO(先进先出)的原则进行排序。ArrayBlockingQueue是线程安全的,可以在多线程环境下安全地使用。

二、内部机制

2.1. 数据结构

ArrayBlockingQueue内部使用一个循环数组作为存储结构。它有两个关键索引:takeIndexputIndex,分别用于从队列中取出元素和向队列中添加元素。当添加元素时,putIndex会递增;当取出元素时,takeIndex会递增。当索引达到数组的末尾时,它们会回到数组的开头,形成一个循环。

2.2. 锁和条件变量

为了保证线程安全,ArrayBlockingQueue使用了一个重入锁(ReentrantLock)以及与之关联的条件变量(Condition)。锁用于保护队列的状态,而条件变量用于在队列为空或满时等待和通知线程。具体来说,ArrayBlockingQueue内部有两个条件变量:notEmptynotFull。当队列满时,生产者线程会等待在notFull条件变量上;当队列空时,消费者线程会等待在notEmpty条件变量上。

2.3. 入队和出队操作
  • 入队操作(put):当调用put方法向队列中添加元素时,如果队列已满,生产者线程会被阻塞,直到队列中有空闲位置。一旦有空闲位置,生产者线程会将元素添加到队列中,并通知可能在等待的消费者线程。
  • 出队操作(take):当调用take方法从队列中取出元素时,如果队列为空,消费者线程会被阻塞,直到队列中有元素可供消费。一旦有元素可供消费,消费者线程会从队列中取出元素,并通知可能在等待的生产者线程。

三、使用场景

  1. 生产者-消费者模式ArrayBlockingQueue非常适合实现生产者-消费者模式。生产者线程将元素添加到队列中,消费者线程从队列中取出元素进行处理。通过阻塞队列,可以很好地协调生产者和消费者之间的速率差异,避免资源的浪费。
  2. 限流:由于ArrayBlockingQueue是一个有界队列,它可以用于实现限流功能。当队列已满时,新的请求会被阻塞或拒绝,从而保护系统免受过多的请求冲击。
  3. 任务调度:在并发编程中,ArrayBlockingQueue可以用作任务调度器的一部分。将任务作为元素添加到队列中,然后由工作线程从队列中取出任务进行处理。这种方式可以实现任务的异步执行和资源的有效利用。

四、最佳实践

  1. 合理设置队列大小:在使用ArrayBlockingQueue时,应根据实际需求合理设置队列的大小。过小的队列可能导致频繁的阻塞和上下文切换;过大的队列可能导致内存浪费和长时间的等待。
  2. 避免在队列中存储大量数据:由于ArrayBlockingQueue是基于数组的实现,每个元素都会占用一定的内存空间。因此,应避免在队列中存储大量数据,以减少内存消耗和垃圾回收的压力。可以将数据拆分成较小的单元进行传输和处理。
  3. 注意线程安全:虽然ArrayBlockingQueue本身是线程安全的,但在使用过程中仍需注意线程安全的问题。例如,在多个线程同时访问队列时,应确保对队列的访问是原子的,以避免竞态条件和数据不一致的问题。
  4. 优雅地处理中断:当线程在等待从队列中取出元素或向队列中添加元素时,可能会被中断。在编写代码时,应优雅地处理这些中断情况,例如通过捕获InterruptedException并适当地响应中断请求。
  5. 使用try-with-resources语句:在使用ArrayBlockingQueue的迭代器时,建议使用try-with-resources语句来自动关闭迭代器。这样可以确保在迭代过程中及时释放资源,避免资源泄漏的问题。

五、ArrayBlockingQueue实现生产者-消费者

下面是一个使用ArrayBlockingQueue实现的稍微复杂的生产者-消费者示例。代码中模拟一个生产者线程生产数据,多个消费者线程消费数据的场景,并且消费者在处理完数据后会将结果存回另一个阻塞队列中以供后续处理。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class ProducerConsumerWithArrayBlockingQueue {public static void main(String[] args) {// 创建一个容量为10的ArrayBlockingQueue作为生产者和消费者的共享队列BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);// 创建一个容量为5的ArrayBlockingQueue用于存储消费者的处理结果BlockingQueue<Integer> resultQueue = new ArrayBlockingQueue<>(5);// 创建一个AtomicInteger作为数据生成的计数器AtomicInteger counter = new AtomicInteger();// 创建一个生产者线程Thread producer = new Thread(() -> {try {for (int i = 0; i < 20; i++) {int item = counter.incrementAndGet();System.out.println("生产者生产数据:" + item);// 将数据放入队列中queue.put(item);// 稍微延迟一下,模拟生产数据的时间消耗Thread.sleep(200);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 创建一个固定线程池的ExecutorService用于执行消费者任务ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交3个消费者任务到线程池中for (int i = 0; i < 3; i++) {executorService.submit(() -> {try {while (true) {// 从队列中取出数据int item = queue.take();// 处理数据(此处仅打印作为示例)System.out.println("消费者" + Thread.currentThread().getId() + "消费数据:" + item);// 假设处理后的数据是原始数据的平方int processedItem = item * item;// 将处理后的结果存入结果队列中resultQueue.put(processedItem);// 稍微延迟一下,模拟处理数据的时间消耗Thread.sleep(500);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 启动生产者线程producer.start();// 等待生产者线程完成try {producer.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 关闭ExecutorService(这将导致消费者线程中断)executorService.shutdown();try {// 等待一段时间,让消费者线程处理剩余的数据if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {executorService.shutdownNow(); // 如果超时则强制关闭消费者线程}} catch (InterruptedException e) {executorService.shutdownNow();Thread.currentThread().interrupt();}// 处理结果队列中的数据(此处仅打印作为示例)while (!resultQueue.isEmpty()) {System.out.println("处理结果:" + resultQueue.poll());}}
}
  • 在上面的代码中,我们定义了两个阻塞队列queueresultQueue,一个用于生产者和消费者之间传递数据,另一个用于存储消费者的处理结果。

  • 我们还使用了一个AtomicInteger作为数据生成的计数器。生产者线程每次生产一个数据就将其放入queue中,而消费者线程则从queue中取出数据进行处理,并将处理结果放入resultQueue中。

  • 最后,我们在主线程中等待生产者线程完成后,关闭消费者线程的ExecutorService,并处理resultQueue中的剩余数据。

需要注意的是,在实际的生产环境中,消费者线程通常会有退出条件,而不是无限循环地处理数据。在这个示例中,由于我们设置了executorService.awaitTermination的超时时间,所以当超时发生时,会强制关闭消费者线程。但是,在更复杂的场景下,我们可能需要使用其他机制来优雅地关闭消费者线程,例如使用一个特殊的结束信号或定期检查某个关闭标志。

请注意,在ArrayBlockingQueue中,queue.isEmpty()并不是一个可靠的退出条件,因为在多线程环境下,你可能会遇到竞态条件的问题。更可靠的方式是使用一个特殊的结束信号或定期检查某个关闭标志来退出循环。

六、总结

ArrayBlockingQueue是Java并发编程中一个非常有用的数据结构。它提供了一个高效、线程安全的有界阻塞队列实现,适用于多种场景如生产者-消费者模式、限流和任务调度等。在使用过程中,我们应注意合理设置队列大小、避免存储大量数据、注意线程安全、优雅地处理中断以及使用try-with-resources语句等最佳实践。通过深入了解ArrayBlockingQueue的内部机制和最佳实践,我们可以更好地利用它来解决并发编程中的挑战。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻

相关文章:

  • Python最常用的库
  • 从零开始利用MATLAB进行FPGA设计(三)将Simulink模型转化为定点数据类型
  • C# RAM Stable Diffusion 提示词反推 Onnx Demo
  • 基于 Spark 的电商用户行为分析系统
  • AI智能分析网关V4将HTTP消息推送至安防监控视频汇聚EasyCVR平台的操作步骤
  • 大语言模型(LLM)Token 概念
  • 如何配置Apache的反向代理
  • Linux动态库*.so函数名修改
  • 动态规划 Leetcode 377 组合总和IV
  • 记事小本本
  • web学习笔记(三十三)
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的稻田虫害检测系统详解(深度学习+Python代码+UI界面+训练数据集)
  • Pytorch搭建AlexNet 预测实现
  • 分布式调用与高并发处理(二)| Dubbo
  • 单片机FLASH深度解析和编程实践(上)
  • [笔记] php常见简单功能及函数
  • 《Java编程思想》读书笔记-对象导论
  • angular2 简述
  • ECMAScript6(0):ES6简明参考手册
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • JS+CSS实现数字滚动
  • Kibana配置logstash,报表一体化
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 给新手的新浪微博 SDK 集成教程【一】
  • 前端技术周刊 2019-01-14:客户端存储
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 由插件封装引出的一丢丢思考
  • 在Unity中实现一个简单的消息管理器
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​【已解决】npm install​卡主不动的情况
  • ​比特币大跌的 2 个原因
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • (02)Hive SQL编译成MapReduce任务的过程
  • (3)STL算法之搜索
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (ZT)一个美国文科博士的YardLife
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (三)Honghu Cloud云架构一定时调度平台
  • (转)socket Aio demo
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .Net 代码性能 - (1)
  • .NET项目中存在多个web.config文件时的加载顺序
  • [2024] 十大免费电脑数据恢复软件——轻松恢复电脑上已删除文件
  • [Android] Amazon 的 android 音视频开发文档
  • [C#]DataTable常用操作总结【转】
  • [CC2642r1] ble5 stacks 蓝牙协议栈 介绍和理解
  • [daily][archlinux][game] 几个linux下还不错的游戏
  • [Java]快速入门优先队列(堆)手撕相关面试题
  • [LeetCode] Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列
  • [Linux] 用LNMP网站框架搭建论坛