阻塞队列BlockingQueue详解

news2024/9/9 5:32:55

一、阻塞队列介绍

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

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

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

相关文章

【C++奇遇记】初探名称空间

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集 数据库专栏 初阶数据结构 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如…

Ubuntu篇——Ubuntu20.04备份成ISO镜像文件并安装到其他电脑上(完整步骤)

注意&#xff0c;此方法制作的镜像&#xff0c;仅限于相同硬件配置的电脑安装&#xff0c;否则可能会发生某些驱动不兼容导致无法安装的情况。&#xff08;例如英伟达显卡的笔记本电脑&#xff0c;很大概率无法安装你在其他显卡电脑上制作的镜像。&#xff09; 一、安装systemb…

设计模式行为型——命令模式

目录 什么是命令模式 命令模式的实现 命令模式角色 命令模式类图 命令模式举例 命令模式代码实现 命令模式的特点 优点 缺点 使用场景 注意事项 什么是命令模式 命令模式&#xff08;Command Pattern&#xff09;是一种数据驱动的设计模式&#xff0c;它属…

一篇关于CPU的硬核知识

不管你玩硬件还是做软件&#xff0c;你的世界都少不了计算机最核心的 —— CPU。 01CPU是什么&#xff1f; CPU与计算机的关系就相当于大脑和人的关系&#xff0c;它是一种小型的计算机芯片&#xff0c;通常嵌入在电脑的主板上。CPU的构建是通过在单个计算机芯片上放置数十亿…

iOS开发-UIScrollView嵌套tableView实现顶部tab横向切换

iOS开发-UIScrollView嵌套tableView实现顶部tab横向切换 通过ScollView嵌套两个TableView左右切换功能 一、UIScollView UIScrollView可滚动控件&#xff0c;这里初始化需要设置_backScollView.pagingEnabled YES; 代码如下 _backScollView [[UIScrollView alloc] initWi…

ARP协议请求

文章目录 作用请求与应答流程数据包ARP协议以太网帧协议具体应用 作用 通过 IP地址 查找 MAC地址。 请求与应答流程 A&#xff1a;数据发送主机 B&#xff1a;目标主机 目前只知道目标主机IP地址&#xff0c;想把数据发送过去&#xff0c;需要查询到目标主机的MAC地址&#x…

构建基于大模型的Autonomous Agents案例(一)

构建基于大模型的Autonomous Agents案例 1.1 Autonomous Agents原理机制 在本节中&#xff0c;我们将聚焦于LangChain上的自治代理&#xff08;Autonomous Agents on LangChain&#xff09;。自治代理是当前业界最热门的话题之一&#xff0c;特别是在企业级应用中。当然&#x…

关于综合能源智慧管理系统的架构及模式规划的研究

安科瑞 华楠 摘 要&#xff1a;探讨了国内外能源互联网的研究发展&#xff0c;分析了有关综合智慧能源管理系统的定位&#xff0c;以及系统的主要特点&#xff0c;研究了综合智慧能源管理系统的构架以及模式规划。 关键词&#xff1a;综合能源&#xff1b;智慧管理系统&#…

MySql操作进阶

目录 1.多表联查 1.1内连接 1.2左连接和右连接 1.3自连接 2.子查询 3.合并查询 1.多表联查 多表联查实际上就是对多张表中的数据合并在一起进行查询&#xff0c;具体合并多张表中的数据方式为&#xff1a;取笛卡尔积的方式进行合并。 但仅是以笛卡尔积的方式合并表较为…

网络安全进阶学习第九课——SQL注入介绍

文章目录 一、什么是注入二、什么是SQL注入三、SQL注入产生的原因四、SQL注入的危害五、SQL注入在渗透中的利用1、绕过登录验证&#xff1a;使用万能密码登录网站后台等。2、获取敏感数据3、文件系统操作4、注册表操作5、执行系统命令 六、如何挖掘SQL注入1、SQL注入漏洞分类按…

无涯教程-Lua - Arrays(数组)

数组是对象的有序排列&#xff0c;可以是包含行集合的一维数组&#xff0c;也可以是包含多行和多列的多维数组。 在Lua中&#xff0c;数组是使用带有整数的索引表实现的。数组的大小不是固定的&#xff0c;并且可以根据无涯教程的要求(取决于内存限制)来增长。 一维数组 一维…

WEB应用程序数据库防守篇之预防SQL漏洞注入

前言: 国内目前几乎市面上所有的web应用程序都使用关系型数据库来存储用户的数据信息&#xff0c;可以这么说&#xff0c;数据库是一个项目&#xff0c;一个互联网公司乃至国家最重要的数据中心。如果数据库被入侵成功&#xff0c;所引发的后果将是灾难性&#xff0c;无法想象…

JavaSE 【类和对象】(3)(重点:代码块、对象的打印)

目录 一、代码块 1.静态代码块 2.构造代码块/实例代码块 3.总结 代码块执行的顺序&#xff1a; 1.静态代码块 2.构造代码块&#xff08;实例代码块&#xff09; 3.执行对应的构造方法 其中&#xff1a;静态的只执行一次 二、 对象的打印 有一个快速打印的方法&#x…

scrapy框架简单实现豆瓣评分爬取案例

豆瓣网址&#xff1a;https://movie.douban.com/top250 1.创建scrapy框架 scrapy startproject 项目名(scrapy_test_one)创建好以后的目录是这样的 2.创建spider文件 在spiders目录下创建一个spider_one.py文件&#xff0c;可以随意命名&#xff0c;该文件主要是让我们进行数…

MaxPatrol SIEM 增加了一套检测供应链攻击的专业技术

我们为 MaxPatrol SIEM 信息安全事件监控系统增加了一套新的专业技术。 该产品可帮助企业防范与供应链攻击相关的威胁。 此类攻击正成为攻击者的首要目标&#xff1a;它们以软件开发商和供应商为目标&#xff0c;网络犯罪分子通过他们的产品进入最终目标的基础设施。 因此&a…

【数据结构】——线性表的相关习题

目录 题型一&#xff08;顺序表的存储结构&#xff09;题型二&#xff08;链表的判空&#xff09;题型三&#xff08;单链表的建立&#xff09;题型四&#xff08;顺序表、单链表的插入删除&#xff09; 题型一&#xff08;顺序表的存储结构&#xff09; 1、线性表的顺序存储结…

Framework开发的热度从未下降~

近几日&#xff0c;统计局也发布了就业相关数据&#xff0c;全国失业青年达600多万&#xff0c;面对此数据&#xff0c;我们能想到的是实际的失业人数肯定会比公布的数据要多很多&#xff0c;尤其是表示 “一周工作一小时以上” 也纳入了就业范围。 反观有一批Android开发在如此…

JavaWeb之HTML基础篇(一)

系列文章目录 HTML基础篇&#xff08;一&#xff09; 文章目录 系列文章目录HTML基础篇&#xff08;一&#xff09;[TOC](文章目录) 前言一、HTML简介1.1介绍1.2HTML文件的书写规范1.3 HTML标签介绍1.4 HTML常见的标签 二、CSS的简介2.1css技术介绍2.2 CSS与HTML结合的三种方式…

org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter-若依二开报错(已解决)

阿丹-问题描述&#xff1a; 今天在复制已经拆解过后的若依system模块删除掉中间的一些东西之后&#xff0c;出现了一个问题。已启动就报错。报错截图如下。 问题解析&#xff1a; 身边不少二开若依的小伙伴都遇到了这个问题。 这个异常是因为在启动应用程序时&#xff0c;Sprin…

杜教筛的小结

总所周知&#xff0c;杜教筛是一个可以快速求积性函数前缀和的工具&#xff0c;为了快速理解杜教筛&#xff0c;自己给自己写了一个文章快速理解。 它可以在O(n2/3)的复杂度快速求出某个积性函数的前缀和。 例如&#xff0c;我们想要知道 f f f函数的前缀和&#xff0c;我们可…