阻塞队列BlockingQueue详解
一、阻塞队列介绍
1、队列

队列入队从队首开始添加,直至队尾;出队从队首出队,直至队尾,所以入队和出队的顺序是一样的
Queue接口
- add(E) :在指定队列容量条件下添加元素,若成功返回true,若当前队列没有可用空间抛出IllegalStateException异常
- offer(E):在指定队列容量条件下添加元素,若成功返回true,若当前队列没有可用空间返回false
- remove():返回并删除此队列的头部元素,若队列为空会抛出异常
- poll():返回并删除此队列的头部元素,若队列为空会返回null
- element():返回头部元素,但不删除,队列为空会抛出异常
- peek():返回头部元素,但不删除,队列为空返回null
2、阻塞队列
BlockingQueue规范定义了添加和删除阻塞队列的方法,很多阻塞队列都是基于BlockingQueue实现的,具体原理:当阻塞队列插入数据时,如果队列已满,线程会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程会阻塞等待直到队列非空
1)BlockingQueue接口

- put():将指定元素插入队列,如果必要等待队列空间变为可用
- take():返回并删除队列中的头部元素,如果必要直到等待某个元素可用
- offer(E, long, TimeUnit):将指定的元素插入此队列,指定的等待时间等待必要的可用空间
- poll(long, TimeUnit):返回并删除此队列的头部元素,指定的等待时间,直到等待某个元素可用
2)应用场景
- 线程池:线程池中线程创建的个数超过核心线程数,会放入到等待队列中,如果队列空了,核心线程又没有要处理的任务,会进入等待,直到队列中有新的任务
- 生产者-消费者模式:当生产者线程发现队列满了会陷入等待,直到有消费者线程进行消费并唤醒生产者线程;当消费者线程发现队列中没有可处理消息会陷入等待,直到生产者线程进行生产并唤醒消费者线程,阻塞队列可以避免线程间的竞争
- 消息队列:可以把消息放到队列中,进行消息的异步处理
- 缓存系统:使用contains()方法判断是否包含某个元素,利用阻塞队列来缓存数据,避免多线程更新缓存的竞争
- 并发任务处理:将任务提交到队列中,消费之后出队,避免重复消费
3、JUC包下的阻塞队列
二、ArrayBlockingQueue
ArrayBlockingQueue采用Object数组方式存储数据,创建ArrayBlockingQueue必须指定容量大小,属于有界队列,采用ReentrantLock保证线程安全,如果生产速度和消费速度基本匹配的情况下,使用ArrayBlockingQueue是个不错选择
1、使用
public class ArrayBlockingQueueTest {private static final int QUEUE_CAPACITY = 5;private static final int PRODUCER_DELAY_MS = 1000;private static final int CONSUMER_DELAY_MS = 2000;public static void main(String[] args) throws InterruptedException {// 创建一个容量为QUEUE_CAPACITY的阻塞队列BlockingQueue<String> queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);// 创建一个生产者线程Runnable producer = () -> {while (true) {try {// 在队列满时阻塞queue.put("producer");System.out.println("生产了一个元素,队列中元素个数:" + queue.size());Thread.sleep(PRODUCER_DELAY_MS);} catch (InterruptedException e) {e.printStackTrace();}}};new Thread(producer).start();// 创建一个消费者线程Runnable consumer = () -> {while (true) {try {// 在队列为空时阻塞String element = queue.take();System.out.println("消费了一个元素,队列中元素个数:" + queue.size());Thread.sleep(CONSUMER_DELAY_MS);} catch (InterruptedException e) {e.printStackTrace();}}};new Thread(consumer).start();}
}
生产者少休眠1s,生产的快,当生产者添加第六个元素时会陷入等待
2、源码分析



- items:数组元素数组
- takeIndex:下一个待取出元素索引
- putIndex:下一个待添加元素索引
- count:元素个数
- lock:内置锁
- notEmpty:消费者
- notFull:生产者
入队详解
https://www.processon.com/view/link/64c8c537b9f7806c73dadbb4
出队详解
https://www.processon.com/view/link/64c8c92fb9f7806c73daea85
为什么ArrayBlockingQueue对数组操作要设计成双指针?
如果用一个指针,对数组的删除或者添加操作,数组中的元素都要往前或者往后移动,这样导致时间复杂度为O(n),而使用双指针可以前移后移,可以提升操作的性能,时间复杂度为O(1)
三、LinkedBlockingQueue
LinkedBlockingQueue是基于链表实现的阻塞队列,队列默认大小为Integer.MAX_VALUE,由于这个数值比较大,LinkedBlockingQueue也被称为无界队列,LinkedBlockingQueue每个元素都会占用内存,为防止OOM还是设置一个队列大小
1、使用
和ArrayBlockingQueue使用基本差不多

- LinkedBlockingQueue():队列大小为2的32次方减1
- LinkedBlockingQueue(Collection<? extends E>):队列大小为2的32次方减1,按照传入集合初始化队列数据
- LinkedBlockingQueue(int):传入参数指定队列大小
2、源码分析
相比ArrayBlockingQueue读写只一把独占锁的实现,LinkedBlockingQueue读写分了两把锁

- item:元素存储的数据
- next:下一个节点,单项链表结构
- capacity:队列容量
- count:元素数量
- head:链表表头
- last链表表尾
- takeLock:出队操作竞争的锁对象
- notEmpty:当队列无元素时,会让进行takeLock的线程陷入等待,直到有线程唤醒
- putLock:入队操作竞争的锁对象
- notFull:当队列满了,会让进行putLock的线程陷入等待,直到有线程唤醒

初始化LinkedBlockingQueue对象时,会创建一个属性item为null的Node对象
入队详解
https://www.processon.com/view/link/64c8f6d5b9f7806c73db4fc9
出队详解
https://www.processon.com/view/link/64c8ff0e7807695f1493090f
3、LinkedBlockingQueue和ArrayBlockingQueue对比
- 队列大小:ArrayBlockingQueue必须指定容量大小,LinkedBlockingQueue可以不指定,LinkedBlockingQueue如果添加比删除快会导致OOM
- 数组存储容器不同:ArrayBlockingQueue采用数组存储数据,LinkedBlockingQueue采用对象链表方式存储数据;就因为会产生Node对象,并发量大时会对gc产生较大的影响
- ArrayBlockingQueue添加和删除都是争抢同一个锁资源,LinkedBlockingQueue添加和删除进行了锁分离,LinkedBlockingQueue高并发场景下可以并行的进行入队和出队操作
四、DelayQueue
可以使用队列消息延迟消费,实现接口回调通知、token超时失效、订单超时失效
1、使用
public class DelayQueueTest {public static void main(String[] args) throws InterruptedException {DelayQueue<Order> delayQueue = new DelayQueue<>();delayQueue.put(new Order("order1", System.currentTimeMillis(), 5000));delayQueue.put(new Order("order2", System.currentTimeMillis(), 2000));delayQueue.put(new Order("order3", System.currentTimeMillis(), 3000));while (!delayQueue.isEmpty()) {Order take = delayQueue.take();System.out.println("处理订单:"+take.getOrderId());}}static class Order implements Delayed {private String orderId;private long createTime;private long delayTime;public Order(String orderId, long createTime, long delayTime) {this.orderId = orderId;this.createTime = createTime;this.delayTime = delayTime;}public String getOrderId() {return orderId;}@Overridepublic long getDelay(TimeUnit unit) {// 订单创建时间+延迟时间-当前时间=剩余延迟时间long diff = createTime + delayTime - System.currentTimeMillis();return unit.convert(diff, unit);}@Overridepublic int compareTo(Delayed o) {// 比较两个订单之间差多长时间long diff = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);return Long.compare(diff, 0);}}
}
2、源码分析

- lock:用于保证线程安全
- q: 优先级队列,存储元素,用于保证延迟低的优先执行
- leader:用于标记当前是否有线程在排队(仅用于取元素时) leader 指向的是第一个从队列获取元素阻塞的线程
- available:条件,用于表示现在是否有可取的元素 当新元素到达,或新线程可能需要成为leader时被通知
入队详解
https://www.processon.com/view/link/64c91879470d721c4e3be985
出队详解
https://www.processon.com/view/link/64c9185fc1af4746895281e7
五、如何选择适合的阻塞队列
1、选择策略
通常我们可以从以下 5 个角度考虑,来选择合适的阻塞队列:
功能
第 1 个需要考虑的就是功能层面,比如是否需要阻塞队列帮我们排序,如优先级排序、延迟执行等。如果有这个需要,我们就必须选择类似于 PriorityBlockingQueue 之类的有排序能力的阻塞队列。
容量
第 2 个需要考虑的是容量,或者说是否有存储的要求,还是只需要“直接传递”。在考虑这一点的时候,我们知道前面介绍的那几种阻塞队列,有的是容量固定的,如 ArrayBlockingQueue;有的默认是容量无限的,如 LinkedBlockingQueue;而有的里面没有任何容量,如 SynchronousQueue;而对于 DelayQueue 而言,它的容量固定就是 Integer.MAX_VALUE。所以不同阻塞队列的容量是千差万别的,我们需要根据任务数量来推算出合适的容量,从而去选取合适的 BlockingQueue。
能否扩容
第 3 个需要考虑的是能否扩容。因为有时我们并不能在初始的时候很好的准确估计队列的大小,因为业务可能有高峰期、低谷期。如果一开始就固定一个容量,可能无法应对所有的情况,也是不合适的,有可能需要动态扩容。如果我们需要动态扩容的话,那么就不能选择 ArrayBlockingQueue ,因为它的容量在创建时就确定了,无法扩容。相反,PriorityBlockingQueue 即使在指定了初始容量之后,后续如果有需要,也可以自动扩容。所以我们可以根据是否需要扩容来选取合适的队列。
内存结构
第 4 个需要考虑的点就是内存结构。我们分析过 ArrayBlockingQueue 的源码,看到了它的内部结构是“数组”的形式。和它不同的是,LinkedBlockingQueue 的内部是用链表实现的,所以这里就需要我们考虑到,ArrayBlockingQueue 没有链表所需要的“节点”,空间利用率更高。所以如果我们对性能有要求可以从内存的结构角度去考虑这个问题。
性能
第 5 点就是从性能的角度去考虑。比如 LinkedBlockingQueue 由于拥有两把锁,它的操作粒度更细,在并发程度高的时候,相对于只有一把锁的 ArrayBlockingQueue 性能会更好。另外,SynchronousQueue 性能往往优于其他实现,因为它只需要“直接传递”,而不需要存储的过程。如果我们的场景需要直接传递的话,可以优先考虑 SynchronousQueue。
2、线程池对于阻塞队列的选择
线程池有很多种,不同种类的线程池会根据自己的特点,来选择适合自己的阻塞队列。
Executors类下的线程池类型:
- FixedThreadPool(SingleThreadExecutor 同理)选取的是 LinkedBlockingQueue
- CachedThreadPool 选取的是 SynchronousQueue
- ScheduledThreadPool(SingleThreadScheduledExecutor同理)选取的是延迟队列
相关文章:
阻塞队列BlockingQueue详解
一、阻塞队列介绍 1、队列 队列入队从队首开始添加,直至队尾;出队从队首出队,直至队尾,所以入队和出队的顺序是一样的 Queue接口 add(E) :在指定队列容量条件下添加元素,若成功返回true,若当前…...
pygame贪吃蛇游戏
pygame贪吃蛇游戏 贪吃蛇游戏通过enter键启动,贪吃蛇通过WSAD进行上下左右移动,每次在游戏区域中随机生成一个食物,每次吃完食物后,蛇变长并且获得积分;按空格键暂停。 贪吃蛇 import random, sys, time, pygame from …...
Mac系统下使用远程桌面连接Windows系统
一、远程桌面工具 Microsoft Remote Desktop 二、下载地址 https://go.microsoft.com/fwlink/?linkid868963 三、下载并安装 四、添加远程PC PC name:云服务器IP。 User account: 添加系统用户 PC name:远程桌面 IP 地址User account:可以选择是…...
使用 OpenCV 和深度学习对黑白图像进行着色
在本文中,我们将创建一个程序将黑白图像(即灰度图像)转换为彩色图像。我们将为此程序使用 Caffe 着色模型。您应该熟悉基本的 OpenCV 功能和用法,例如读取图像或如何使用 dnn 模块加载预训练模型等。现在让我们讨论实现该程序所遵循的过程。 给定一张灰度照片作为输入,本文…...
从价值的角度看,为何 POSE 通证值得长期看好
PoseSwap 是 Nautilus Chain 上的首个 DEX,基于 Nautilus Chain 也让其成为了首个以模块化构建的 Layer3 架构的 DEX。该 DEX 本身能够以 Dapp 层(Rollup)的形态,与其他应用层并行化运行。...
pytorch的CrossEntropyLoss交叉熵损失函数默认reduction是平均值
pytorch中使用nn.CrossEntropyLoss()创建出来的交叉熵损失函数计算损失默认是求平均值的,即多个样本输入后获取的是一个均值标量,而不是样本大小的向量。 net nn.Linear(4, 2) loss nn.CrossEntropyLoss() X torch.rand(10, 4) y torch.ones(10, dt…...
OKR管理策略:为开发团队注入动力
引言 在这个快速变化的世界中,公司需要迅速应对市场变化,并保持其目标和战略的清晰性和一致性。而OKR(Objectives and Key Results)正是这个挑战的解决方案之一。OKR的实施可以帮助开发团队明确目标,关注关键结果&…...
C++二叉搜索树剖析
目录 🍇二叉搜索树概念🍈二叉搜索树查找🍉二叉搜索树的插入🍊二叉搜索树的删除🍍二叉搜索树的查找、插入、删除实现🍋二叉搜索树的应用🥭二叉搜索树的性能分析🍓总结 🍇二…...
升级你的GitHub终端认证方式:从密码到令牌
升级你的GitHub终端认证方式:从密码到令牌 前言 GitHub官方在2021年8月14日进行了一次重大改变,它将终端推送代码时所需的身份认证方式从密码验证升级为使用个人访问令牌(Personal Access Token)。这个改变引起了一些新的挑战&am…...
【力扣】链表题目总结
文章目录 链表基础题型一、单链表翻转、反转、旋转1.反转链表2.反转链表II——反转部分链表3.旋转链表4.K个一组翻转链表5.反转偶数长度组的节点 二、删除单链表中的结点1.删除链表的结点2.删除未排序链表中的重复节点3.删除已排序链表中的重复元素I——重复元素只剩下一个4.删…...
Thunar配置自定义动作
Add “Copy To” and “Move To” custom actions in Thunar file manager | For the record 1.在此打开终端 图标-应用程序:utilities-terminal 命令:exo-open --working-directory %f --launch TerminalEmulator 文件类型:* 目录 2.右键增…...
Python 开发工具 Pycharm —— 使用技巧Lv.3
单步执行调试 1: 鼠标左键单击红点是断点行 2:甲虫样式是进行调试方式运行,鼠标左键单击点击 3: 单步运行图标,点击让程序运行一行 4: 步入步出,可以进入当前代码行函数内 5:重新运行…...
51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验三 LED流水灯
目录 前言 一、原理图及知识点介绍 二、代码分析 知识点五:#include 中的库函数解析 _crol_,_irol_,_lrol_ _cror_,_iror_,_lror_ _nop_ _testbit_ 前言 第一个实验:51单片机(普中HC6800-EM3 V3.0…...
深度学习与计算机相结合:直播实时美颜SDK的创新之路
时下,实时美颜技术就成为了直播主们的得力工具,它可以在直播过程中即时处理视频画面。而支持实时美颜功能的SDK更是推动了这项技术的发展,让直播主和普通用户都能轻松使用美颜功能。 一、美颜技术的演进 早期的美颜技术主要依赖于简单的图…...
Unity寻找子物体的方法
1.GetComponentsInChildren() 查找单个子物体 GameObject childObjectGetComponentInChildren<Transform>(); 查找多个子物体 Transform[] myTransforms GetComponentsInChildren<Transform>(); foreach (var child in myTransforms){ Debug.Log(child.name…...
车载软件架构 —— 车载软件安全启动关键技术解读
车载软件架构 —— 车载软件安全启动关键技术解读 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生…...
2023-08-05——JVM Method Area(方法区)
方法区 Method Area(方法区) 方法区是指被所有线程共享的,字段和方法字节码,以及一些特殊方法,如构造函数,接口代码在此定义,简单的说就是所有的定义方法信息都保存在此区域,此区域…...
【前端知识】React 基础巩固(四十六)——自定义Hook的应用
React 基础巩固(四十六)——自定义Hook的应用 一、自定义Hook的应用 自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上而言,它并不算React的特性。 实现组件创建/销毁时打印日志 import React, { memo, useEffect, useState } from "react…...
Swish - Mac 触控板手势窗口管理工具[macOS]
Swish for Mac是一款Mac触控板增强工具,借助直观的两指轻扫,捏合,轻击和按住手势,就可以从触控板上控制窗口和应用程序。 Swish for Mac又不仅仅只是一个窗口管理器,Swish具有28个易于使用的标题栏,停靠栏…...
【雕爷学编程】MicroPython动手做(31)——物联网之Easy IoT 2
1、物联网的诞生 美国计算机巨头微软(Microsoft)创办人、世界首富比尔盖茨,在1995年出版的《未来之路》一书中,提及“物物互联”。1998年麻省理工学院提出,当时被称作EPC系统的物联网构想。2005年11月,国际电信联盟发布《ITU互联网…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...

