阻塞队列BlockingQueue实战及其原理分析

news2024/9/27 19:20:00

一,队列

1. 队列

  • 是限定在一端进行插入,另一端进行删除的特殊线性表。
  • 允许出队的一端称为队头,允许入队的一端称为队尾。
  • 先进先出(FIFO)线性表。

    1.1 Queue接口

  2. 阻塞队列(BlockingQueue

        阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式: 当阻塞队列插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从 阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。 并发包下很多高级同步类的实现都是基于BlockingQueue实现的。

2.1 BlockingQueue接口

方法
抛出异常
返回特定值
阻塞
阻塞特定时间
入队
add(e)
offer(e)
put(e)
offer(e, time,
unit)
出队
remove()
poll()
take()
poll(time, unit)
获取队首元素
element()
peek()
不支持
不支持

2.2 应用场景

        1. 线程池:线程池中的任务队列通常是一个阻塞队列。当任务数超过线程池的容量时,新提交的任务将被放入任务队列中等待执行。线程池中的工作线程从任务队列中取出任务进行处理,如果队列为空,则工作线程会被阻塞,直到队列中有新的任务被提交。

        2.生产者-消费者模型:在生产者-消费者模型中,生产者向队列中添加元素,消费者从队列中取出元素进行处理。阻塞队列可以很好地解决生产者和消费者之间的并发问题,避免线程间的竞争和冲突。

        3.消息队列:消息队列使用阻塞队列来存储消息,生产者将消息放入队列中,消费者从队列中取出消息进行处理。消息队列可以实现异步通信,提高系统的吞吐量和响应性能,同时还可以将不同的组件解耦,提高系统的可维护性和可扩展性。

        4.缓存系统:缓存系统使用阻塞队列来存储缓存数据,当缓存数据被更新时,它会被放入队列中,其他线程可以从队列中取出最新的数据进行使用。使用阻塞队列可以避免并发更新缓存数据时的竞争和冲突。

        5.并发任务处理:在并发任务处理中,可以将待处理的任务放入阻塞队列中,多个工作线程可以从队列中取出任务进行处理。使用阻塞队列可以避免多个线程同时处理同一个任务的问题,并且可以将任务的提交和执行解耦,提高系统的可维护性和可扩展性。

3. JUC包下的阻塞队列 

        BlockingQueue 接口的实现类都被放在了 juc 包中,它们的区别主要体现在存储结构上或对元素操作 上的不同,但是对于take与put操作的原理却是类似的。

3.1 常见的阻塞队列

                    队列                                         描述
ArrayBlockingQueue
基于数组结构实现的一个有界阻塞队列
LinkedBlockingQueue
基于链表结构实现的一个无界阻塞队列,指定容
量为有界阻塞队列
PriorityBlockingQueue
支持按优先级排序的无界阻塞队列
DelayQueue
基于优先级队列(PriorityBlockingQueue)实现
的无界阻塞队列
SynchronousQueue
不存储元素的阻塞队列
LinkedTransferQueue
基于链表结构实现的一个无界阻塞队列
LinkedBlockingDeque
基于链表结构实现的一个双端阻塞队列

4. ArrayBlockingQueue

        ArrayBlockingQueue是最典型的有界阻塞队列,其内部是用数组存储元素的,初始化时需要指定容量大小,利用 ReentrantLock 实现线程安全。ArrayBlockingQueue可以用于实现数据缓存、限流、生产者-消费者模式等各种应用。
        在生产者-消费者模型中使用时, 如果生产速度和消费速度基本匹配的情况下,使用
ArrayBlockingQueue是个不错选择; 当如果生产速度远远大于消费速度,则会导致队列填满,大量生产线程被阻塞。

        4.1 模拟生产者-消费者模式

package com.laoyang.Thread.blockingqueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {

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

        4.2 ArrayBlockingQueue的原理

        ArrayBlockingQueue使用独占锁ReentrantLock实现线程安全,入队和出队操作使用同一个锁对象,也就是只能有一个线程可以进行入队或者出队操作;这也就意味着生产者和消费者无法并行操作,在高并发场景下会成为性能瓶颈。

 5.LinkedBlockingQueue

        LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。

5.1 LinkedBlockingQueue原理

        LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。

LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。

5.2 LinkedBlockingQueue与ArrayBlockingQueue对比 

        LinkedBlockingQueue是一个阻塞队列,内部由两个ReentrantLock来实现出入队列的线程安全,由各自的Condition对象的await和signal来实现等待和唤醒功能。它和ArrayBlockingQueue的不同点在于:
  • 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
            
  • 数据存储容器不同 ,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
  • 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
  • 两者的实现队列添加或移除的锁不一样, ArrayBlockingQueue实现的队列中的锁是没有分离的 ,即添加操作和移除操作采用的同一个ReenterLock锁,而 LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量, 也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

 6.DelayQueue

        DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。延迟队列的特点是:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。

        

        它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了 Comparable 接口,所以自然就拥有了比较和排序的能力,代码如下:

6.1 DelayQueue使用

        DelayQueue 实现延迟订单

        在实现一个延迟订单的场景中,我们可以定义一个 Order 类,其中包含订单的基本信息,例如订单编号、订单金额、订单创建时间等。同时,我们可以让 Order 类实现 Delayed 接口,重写 getDelay 和compareTo 方法。在 getDelay 方法中,我们可以计算订单的剩余延迟时间,而在 compareTo 方法中,我们可以根据订单的延迟时间进行比较。
下面是一个简单的示例代码,演示了如何使用 DelayQueue 来实现一个延迟订单的场景:
package com.laoyang.Thread.blockingqueue;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueExample {

    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Order> delayQueue = new DelayQueue<>();

        // 添加三个订单,分别延迟 5 秒、2 秒和 3 秒
        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 order = delayQueue.take();
            System.out.println("处理订单:" + order.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, TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            long diff = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
            return Long.compare(diff, 0);
        }
    }
}

二,如何选择适合的阻塞队列 

        2.1 选择策略

                功能: 比如是否需要阻塞队列帮我们排序,如优先级排序、延迟执行等。如果有这个需要,我们就必须选择类似于 PriorityBlockingQueue 之类的有排序能力的阻塞队 列。

                容量:第 2 个需要考虑的是容量,或者说是否有存储的要求,还是只需要“直接传递”。在考虑这一点的时候,我们知道前面介绍的那几种阻塞队列,有的是容量固定的,如 ArrayBlockingQueue;有的默认是容量无限的,如 LinkedBlockingQueue;而有的里面没有任何容量,如SynchronousQueue;而对于 DelayQueue 而言,它的容量固定就是 Integer.MAX_VALUE。所以不同阻塞队列的容量是千差万别的,我们需要根据任务数量来推算出合适的容量,从而去选取合适的BlockingQueue。

                能否扩容:第 3 个需要考虑的是能否扩容。因为有时我们并不能在初始的时候很好的准确估计队列的大小, 因为业务可能有高峰期、低谷期。如果一开始就固定一个容量,可能无法应对所有的情况,也是不合适的,有可能需要动态扩容。如果我们需要动态扩容的话,那么就不能选择 ArrayBlockingQueue ,因为它的容量在创建时就确定了,无法扩容。相反PriorityBlockingQueue 即使在指定了初始容量之后,后续如果有需要,也可以自动扩容。所以我们可以根据是否需要扩容来选取合适的队列。

                内存结构:第 4 个需要考虑的点就是内存结构。我们分析过 ArrayBlockingQueue 的源码,看到了它的内部结构是“数组”的形式。和它不同的是,LinkedBlockingQueue 的内部是用链表实现的,所以这里就需要我们考虑到,ArrayBlockingQueue 没有链表所需要的“节点”,空间利用率更高。所以如果我们对性能有要求可以从内存的结构角度去考虑这个问题。

                性能:第 5 点就是从性能的角度去考虑。比如 LinkedBlockingQueue 由于拥有两把锁,它的操作粒度更细,在并发程度高的时候,相对于只有一把锁的 ArrayBlockingQueue 性能会更好。另外, SynchronousQueue 性能往往优于其他实现,因为它只需要“直接传递”,而不需要存储的过程。如果我们的场景需要直接传递的话,可以优先考虑 SynchronousQueue。

        2.2 线程池对于阻塞队列的选择

线程池有很多种,不同种类的线程池会根据自己的特点,来选择适合自己的阻塞队列。

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

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

相关文章

Linux系统64位ATT系统调用汇编指令syscall

相关概念 在Linux中syscall是系统调用&#xff08;英文&#xff1a;system call&#xff09;的指令。 想要深入了解syscall的作用&#xff0c;就需要了解特权级别。 现代计算机通常采用名为保护环&#xff08;Protection Rings&#xff09;的机制来保护整个系统的数据和功能&a…

出海 SaaS 企业增长修炼手册2:Kyligence 落地 PLG 是如何避坑的?

在第一篇文章&#xff08;点击阅读 &#x1f449; 出海 SaaS 企业增长修炼手册&#xff1a;聊聊 PLG 的关键指标、技术栈和挑战&#xff09;中&#xff0c;我们分享了 SaaS 企业在落地 PLG 战略中可能遇到的挑战以及相应的解决方案&#xff0c;今天我们将从 Kyligence 亲身实践…

【数据结构】数组和字符串(二):特殊矩阵的压缩存储:对角矩阵——一维数组

文章目录 4.2.1 矩阵的数组表示4.2.2 特殊矩阵的压缩存储a. 对角矩阵的压缩存储结构体初始化元素设置元素获取打印矩阵主函数输出结果代码整合 4.2.1 矩阵的数组表示 【数据结构】数组和字符串&#xff08;一&#xff09;&#xff1a;矩阵的数组表示 4.2.2 特殊矩阵的压缩存储…

Qt之普通项目如何生成DLL(含源码+注释)

文章目录 一、示例图二、普通项目需要改造的内容三、源码&#xff08;创建了一个TestDLL的项目&#xff0c;更改内容主要在pro文件和maindow.h文件&#xff09;TestDLL.promainwindow.hmainwindow.cppmainwindow.ui 总结 一、示例图 使用不同的编译模式编译&#xff0c;会在对…

卷积神经网络的感受野

经典目标检测和最新目标跟踪都用到了RPN(region proposal network)&#xff0c;锚框(anchor)是RPN的基础&#xff0c;感受野(receptive field, RF)是anchor的基础。本文介绍感受野及其计算方法&#xff0c;和有效感受野概念。 1.感受野概念 在典型CNN结构中&#xff0c;FC层(…

一款集成了主流大语言模型以及绘图模型的 APP, 采用 Flutter 开发,代码完全开源!!

一款集成了主流大语言模型以及绘图模型的 APP&#xff0c; 采用 Flutter 开发&#xff0c;代码完全开源&#xff0c;支持以下功能&#xff1a; 支持 OpenAI 的 GPT-3.5&#xff0c;GPT-4 大语言模型支持 Anthropic 的 Claude instant&#xff0c;Claude 2.0 大语言模型支持国产…

Python 框架学习 Django篇 (六) 数据表关联、ORM关联

在后端服务器开发中&#xff0c;特别是前后端分离的架构中数据库是非常重要的&#xff0c;后端主要就是负责管理数据&#xff0c;而我们经常使用的mysql、oracle 都是关系型数据库&#xff0c;什么是关系型数据库&#xff1f;就是建立在关系模型基础上的数据库&#xff0c;而最…

竞赛 深度学习人体跌倒检测 -yolo 机器视觉 opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的人体跌倒检测算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满…

flutter 使用FlutterJsonBeanFactory工具遇到的问题

如下图&#xff0c;使用FlutterJsonBeanFactory工具生成的数据类 但是其中 生成的 import package:null/&#xff0c;导致的错误&#xff1a;Target of URI doesn’t exist: ‘package:null/generated/json/asd.g.dart’ 尝试过的方法&#xff1a; 手动添加包名&#xff0c;…

面试算法39:直方图最大矩形面积

题目 直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组&#xff0c;数组中的数字是直方图中柱子的高。求直方图中最大矩形面积。假设直方图中柱子的宽都为1。例如&#xff0c;输入数组[3&#xff0c;2&#xff0c;5&#xff0c;4&#xff0c;6&am…

前后端交互系统:在Node.js中运行JavaScript

在Node.js中运行JavaScript&#xff0c;您需要编写适用于服务器端的代码&#xff0c;而不是浏览器端的代码。以下是一些示例代码&#xff0c;用于在Node.js中创建一个简单的HTTP服务器并在浏览器中访问它&#xff1a; // 引入Node.js内置的http模块 const http require(http);…

华为ERP,包含哪些内容?技术的先进性体现在哪里?

华为作为全球领先的信息和通信技术&#xff08;ICT&#xff09;解决方案提供商&#xff0c;其企业资源规划&#xff08;ERP&#xff09;系统是一个高度复杂且集成的管理软件平台&#xff0c;用于优化公司内部的业务流程和资源分配。华为ERP系统包括一系列模块和功能&#xff0c…

pytest-yaml 测试平台-2.支持上传YAML格式用例

前言 pytest-yaml 测试平台用例结构是遵循pytest-yaml-yoyo 框架用例规则。 如果你之前已经有用 pytest-yaml-yoyo 框架完成了yaml 用例的编写&#xff0c;可以直接在平台上导入yaml 用例。 yaml 用例 test_用例文件a.yml config:name: 单个用例文件test_a:name: extract提…

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第三章 多线程服务器的适用场合与常用编程模型

本文中的多线程服务器指运行在Linux上的独占式网络应用程序。硬件平台为Intel x86-64系列的多核CPU&#xff0c;单路或双路SMP&#xff08;Symmetric Multi-Processing&#xff0c;对称多处理&#xff0c;它是一种多核处理器架构&#xff0c;其中多个CPU核心共享系统的内存和其…

(三)行为模式:10、策略模式(Strategy Pattern)(C++示例)

目录 1、策略模式&#xff08;Strategy Pattern&#xff09;含义 2、策略模式的UML图学习 3、策略模式的应用场景 4、策略模式的优缺点 &#xff08;1&#xff09;优点&#xff1a; &#xff08;2&#xff09;缺点 5、C实现策略模式的实例 6、策略模式解析 1、策略模式…

解决报错:gnutls_handshake() failed: The TLS connection was non-properly terminated.

执行git clone的时候&#xff0c;出现错误&#xff1a;gnutls_handshake() failed: The TLS connection was non-properly terminated. 如图&#xff1a; 解决方式&#xff1a; 两次重置代理&#xff1a;完美解决 git config --global --unset https.https://github.com.pro…

批量编辑 Outlook 联系人

现状 Outlook 自带的联系人编辑功能无法快速、批量编辑联系人字段使用 Excel 等外部编辑器&#xff0c;可批量编辑联系人 导出联系人到文件 在【联系人】界面&#xff0c;点击【文件】在【文件】界面&#xff0c;点击【打开和导出】–>【导入/导出】在弹出的向导窗口中点…

[C国演义] 第十七章

第十七章 摆动序列最长递增子序列的个数 摆动序列 力扣链接 子序列 ⇒ dp[i]的含义: 以nums[i] 为结尾的所有子序列中的 摆动序列中的最长长度子序列 ⇒ 状态转移方程: 最后一个元素的构成 初始化: 都初始化为 1 遍历方向: 从前往后返回结果: f表 和 g表中的最大值 class …

解决Typora闪退、文件打不开等问题——Typora免费使用教程(也有免费版的)

问题描述&#xff1a; 下好Typora之后打开会闪退&#xff0c;程序运行一会之后就自己关闭了&#xff0c;显示出一片空白&#xff0c;以前写的文件也打不开。 经过一番搜索&#xff0c;发现大佬的文章&#xff0c;给出了很详细的原因分析、解决过程&#xff1b; 具体参考如下&…

【1.2】神经网络:神经元与激活函数

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 神经网络&#xff08;随缘更新&#xff09; ✨特色…