阻塞队列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;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            // 订单创建时间+延迟时间-当前时间=剩余延迟时间
            long diff = createTime + delayTime - System.currentTimeMillis();
            return unit.convert(diff, unit);
        }

        @Override
        public 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同理)选取的是延迟队列

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flask简介与基础入门

一、了解框架 Flask作为Web框架&#xff0c;它的作用主要是为了开发Web应用程序。那么我们首先来了解下Web应用程序。Web应用程序 (World Wide Web)诞生最初的目的&#xff0c;是为了利用互联网交流工作文档。 1、一切从客户端发起请求开始。 所有Flask程序都必须创建一个程序…

webScoket

webScoket是什么&#xff1f; 支持端对端通讯可以由客户端发起&#xff0c;也可以有服务端发起用于消息通知、直播间讨论区、聊天室、协同编辑等 做一个简单的webScoket 客户端配置&#xff1a; 1、新建一个页面叫web-scoket.html <!DOCTYPE html> <html lang"…

【CSS】ios上fixed固定定位的input输入框兼容问题

需求 &#xff1a; 实现一个简单的需求&#xff0c;上方是搜索框并且固定顶部&#xff0c;下方是滚动的内容list 问题 : 若如图上方使用固定定位, 下方用scroll-view, 在安卓上是没有问题的, 但是发现ios上会出现兼容问题 : 问题1: 当content list滚动到中间时再去搜索, 展…

maven引入本地jar包的简单方式【IDEA】【SpringBoot】

前言 想必点进来看这篇文章的各位&#xff0c;都是已经习惯了Maven从中央仓库或者阿里仓库直接拉取jar包进行使用。我也是&#x1f921;&#x1f921;。 前两天遇到一个工作场景&#xff0c;对接三方平台&#xff0c;结果对方就是提供的一个jar包下载链接&#xff0c;可给我整…

明明已经安装字体,但IDEA、CLION无法找到思源黑体/Source Hans Sans的问题解决

IDEA、CLION的Jetbrain系列软件不支持非TrueType的中文字体&#xff0c;而Adobe官方给出的字体却不是TrueType的&#xff0c;所以便会导致Jetbrain系软件无法找到已安装的中文字体&#xff0c;因此我们需要安装TrueType的字体 请在以下Github链接中下载&#xff1a; TrueType思…

java实现钉钉群机器人@机器人获取信息后,机器人回复

1.需求 鉴于需要使用钉钉群机器人回复&#xff0c;人们提出的问题&#xff0c;需要识别提出的问题中的关键词&#xff0c;后端进行处理实现对应的业务逻辑 2.实现方式 用户群机器人&#xff0c;附带提出的问题&#xff0c;后端接收消息后识别消息内容&#xff0c;读取到关键…

使用Three.js创建旋转的立方体

使用Three.js创建旋转的立方体 在本篇技术博客中&#xff0c;我们将介绍如何使用Three.js创建一个简单的场景&#xff0c;其中包含一个旋转的立方体。我们将学习如何设置场景、摄像机、立方体和渲染器&#xff0c;以及如何使用OrbitControls和gsap库来实现立方体的旋转动画和交…

基于Spring Boot的美食分享网站设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于Spring Boot的美食分享网站设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java springboot…

HawkEye设备智能维保平台:助力制药行业设备管理实现数字化转型

随着科技的不断进步和市场竞争的日益激烈&#xff0c;制药行业的设备管理的数字化转型已经成为一个不可逆转的趋势。尤其是在疫情时代&#xff0c;制药企业肩负着重大的社会责任&#xff0c;致使其设备管理的数字化转型之路迫在眉睫。 设备管理的数字化不仅可以提高企业的生产效…

iOS开发-实现热门话题标签tag显示控件

iOS开发-实现热门话题标签tag显示控件 话题标签tag显示非常常见&#xff0c;如选择你的兴趣&#xff0c;选择关注的群&#xff0c;超话&#xff0c;话题等等。 一、效果图 二、实现代码 由于显示的是在列表中&#xff0c;这里整体控件是放在UITableViewCell中的。 2.1 标签…

leetcode 135. 分发糖果

2023.8.1 这道题只从前向后遍历会出各种问题&#xff0c;所以最后决定向前向后各遍历一次。 先定义一个饼干数组biscuits&#xff0c;记录每个孩子的饼干数量&#xff0c;初始化每个孩子饼干数量为1。 然后从前向后遍历、从后向前遍历&#xff0c;使其满足“相邻两孩子评分更高…

【CSS】视频文字特效

效果展示 index.html <!DOCTYPE html> <html><head><title> Document </title><link type"text/css" rel"styleSheet" href"index.css" /></head><body><div class"container"&g…

R语言地理加权回归、主成份分析、判别分析等空间异质性数据分析

在自然和社会科学领域有大量与地理或空间有关的数据&#xff0c;这一类数据一般具有严重的空间异质性&#xff0c;而通常的统计学方法并不能处理空间异质性&#xff0c;因而对此类型的数据无能为力。以地理加权回归为基础的一系列方法&#xff1a;经典地理加权回归&#xff0c;…

英雄的力量【力扣2681】

1、解题思路 将数组按从大到小的顺序排列&#xff0c;i<j&#xff0c;那么以nums[i]开始&#xff0c;nums[j]结尾&#xff0c;i----j中的任意数&#xff0c;组成的排列&#xff0c;其英雄力量都是nums[i]*nums[i]*nums[j]&#xff1b; 若ij&#xff0c;则只有一种排列组合…

微信小程序开发学习之--地图绘制行政区域图

不知道大家有没有感觉就是在做微信小程序地图功能时刚刚接触时候真的感觉好迷茫呀&#xff0c;文档看不懂&#xff0c;资料找不到&#xff0c;就很难受呀&#xff0c;比如我现在的功能就想想绘制出一个区域的轮廓图&#xff0c;主要是为了显眼&#xff0c;效果图如下&#xff1…

xinput1_4.dll丢失的解决方法,三种解决方法分享

xinput1_4.dll是一个动态链接库文件&#xff08;DLL&#xff09;&#xff0c;它是Microsoft DirectX的一部分&#xff0c;用于处理游戏控制器输入。当你的电脑提示xinput1_4.dll文件丢失时&#xff0c;意味着与这个文件相关的游戏或应用程序无法正常运行。 当你的电脑提示xinp…

<C++> 引用

1.引用的概念 引用&#xff08;Reference&#xff09;是一种别名&#xff0c;用于给变量或对象起另一个名称。引用可以理解为已经存在的变量或对象的别名&#xff0c;通过引用可以访问到原始变量或对象的内容。引用在声明时使用 & 符号来定义。 示例&#xff1a; #inclu…

ad+硬件每日学习十个知识点(16)23.7.27 (总线保持、lin报文、逻辑器件手册解读)

文章目录 1.总线保持是怎么实现的&#xff1f;有什么需要注意的&#xff08;驱动电流和电阻&#xff09;&#xff1f;2.LIN报文3.芯片datasheet的features、applications、description看完&#xff0c;应该能大致判断逻辑器件能否满足我们的要求。4.什么是逻辑器件的传输延时&a…

InnoDB存储引擎——事务原理

1.什么是事务 2.redo log 脏页是指缓冲区的数据与磁盘中的数据不一致时的状态。脏页的数据并不是实时刷新的&#xff0c;而是一段时间之后通过后台线程把脏页的数据刷线到磁盘&#xff0c;假如说脏页的数据在往磁盘中刷新的时候出错了&#xff0c;内存中的数据没有刷新到磁盘当…

软件测试技能大赛任务二单元测试试题

任务二 单元测试 执行代码测试 本部分按照要求&#xff0c;执行单元测试&#xff0c;编写java应用程序&#xff0c;按照要求的覆盖方法设计测试数据&#xff0c;使用JUnit框架编写测试类对程序代码进行测试&#xff0c;对测试执行结果进行截图&#xff0c;将相关代码和相关截…