JUC并发编程之ArrayBlockingQueue的底层原理

news2024/11/18 20:48:28
作者简介:
专注于研究Linux内核、Hotspot虚拟机、汇编语言、JDK源码、各大中间件源码等等
喜欢的话,可以三连+关注~

ArrayBlockingQueue的介绍

在JUC包下关于线程安全的队列实现有很多,那么此篇文章讲解ArrayBlockingQueue的实现原理。相对于LinkedBlockingQueue和SynchronousQueue来说,ArrayBlockingQueue效率比较低,但是实现比较容易,从类名也可以看出,这个是基于数组实现的队列。从简单入手,再一步步学习复杂的。

JUC并发编程之LinkedBlockingQueue的底层原理

JUC并发编程之SynchronousQueue的底层原理

    // 因为基于数组实现的队列,全局数组
    final Object[] items;

    // 消费者索引
    int takeIndex;

    // 生产者索引
    int putIndex;

    // 队列总数计数器
    int count;

    // 同步锁,所以生产者和消费者同一时刻只能一个运行。
    final ReentrantLock lock;

    // 消费者条件等待队列
    private final Condition notEmpty;

    // 生产者条件等待队列
    private final Condition notFull;
    

ArrayBlockingQueue实现基于数组,但是DougLea把数组玩的比较灵活,这里是一个循环数组,把一个数组完成了复用。

整体只使用了一把锁,所以生产者和消费者共用一把锁,也即生产的时候不能消费(这里对比LinkedBlockingQueue,所以ArrayBlockingQueue效率比较低)。条件等待队列还是分为了消费者队列和生产者队列。

构造方法把数组和同步锁给初始化了,没啥好讲,那么下面就开始介绍生产者和消费者方法。

put方法(生产者)

// 生产者,带阻塞
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 可响应中断
    lock.lockInterruptibly();
    try {
        // 如果当前队列满了,就去阻塞
        while (count == items.length)
            // 就去阻塞,等待消费线程消费了节点把我唤醒,然后抢锁再去插入。
            notFull.await();
        // 插入节点
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

// 插入数据
private void enqueue(E x) {
        // 当前是单线程,已经外面已经上锁了,能到这里代表已经获取到锁了。
        // 因为当前队列是数组实现的,所以得到当前数组对象
        final Object[] items = this.items;

        // 插入,从0开始
        items[putIndex] = x;

        // ++putIndex调整索引,方便下次插入。
        // 如果当前插入后达到了最大容量,让数组下标又从0开始,循环数组。
        if (++putIndex == items.length)
            // 清零,方便下一轮插入。
            putIndex = 0;

        // 当前队列中总共的数量。
        count++;

        // 唤醒因为当前队列中没有节点而阻塞的消费者线程,
        notEmpty.signal();
    }

要注意这里是一个循环数组,当putIndex生产索引达到数组长度后,把索引清零。此时会不会出现数组越界呢?答案肯定是:不会,因为count全局计数器保证了当队列满了以后,生产者会去条件队列阻塞,等待消费者消费再唤醒。

大致流程如下:

  • 上可响应中断锁

  • 如果队列满了就去阻塞等待

  • 如果没满就往队列(数组)中插入元素

  • 如果生产索引达到了数组长度,清空索引,达到数组复用(循环数组)。

  • 队列元素计数器+1

  • 唤醒消费者节点

  • 释放锁

take方法(消费者)

// 消费者,带阻塞
public E take() throws InterruptedException {
    // 获取到锁
    final ReentrantLock lock = this.lock;

    // 可响应中断锁
    lock.lockInterruptibly();
    try {
        // 如果队列为空,消费者直接去阻塞
        while (count == 0)
            // 阻塞等待被唤醒
            notEmpty.await();

        // 被中断唤醒或者是被生产者唤醒。
        // 此时消费节点。
        return dequeue();
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
        // 得到当前的数组对象。
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")

        // 取出节点
        E x = (E) items[takeIndex];

        // 把节点置为null,方便下次使用。
        items[takeIndex] = null;

        // 如果当前取完节点后,是最后一个节点,那么清空索引,方便下轮使用,循环数组
        if (++takeIndex == items.length)
            takeIndex = 0;

        // 总数量-1
        count--;

        // 
        if (itrs != null)
            itrs.elementDequeued();

        // 唤醒那些因为队列满了而阻塞等待的生产者线程。
        notFull.signal();
        return x;
    }

这里跟生产者方法基本思想一致,存在一个消费索引,当消费索引等于数组长度时,清空索引,达到复用。

大致流程如下:

  • 上可响应中断锁

  • 如果队列为空就去阻塞等待

  • 如果不为空就往队列(数组)中根据takeIndex消费索引获取元素

  • 如果消费索引达到了数组长度,清空索引,达到数组复用(循环数组)。

  • 队列元素计数器-1

  • 唤醒生产者节点

  • 释放锁

总结

循环队列,生产者操作putIndex索引,消费者操作takeIndex索引。队列满了生产者阻塞,等待消费者消费节点唤醒生产者。队列为空消费者阻塞,等待生产者生产节点唤醒消费者。

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

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

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

相关文章

闲置vps出售流量赚钱

原理分析最近,一直在关注着vps赚钱这点事儿,今天就是填一下以前的坑。这里所说的出售流量,可能大家都听说过,把vps流量卖给别人获得一定的报酬。但是你要知道数据中心IP的流量是很便宜的,最贵的是住宅IP和移动流量。图…

别搜了!2023年PMP备考攻略全指南看这里就够了!

一、考试时间 PMP考试是一年四次的,一般在3月、6月、9月、12月份考试(考试时间一般为周六)。 所以如果有想法一定要在这个几个时间点之间备考准备哦。 二,报名流程 一般都是中英文两个官网都报名 1.英文报名 需要在PMI官方网…

Ubuntu救援模式emergency mode笔记

如果是磁盘自检出错,进入救援模式emergency mode,可以参考如下。chatgpt给出提示:如果Ubuntu磁盘自检出错,需要进入救援模式,可以以下步骤操作:在启动画面中,按下Shift键进入Grub菜单。选择「Ad…

Android精准开发——OKHTTP中拦截器原理及实现

1.前言 提到OKHttp大家都不陌生,OKHttp中的拦截器也在大家的项目中或多或少的被使用到,通常我们的使用是这样的 OkHttpClient client new OkHttpClient.Builder() .addInterceptor(new LoggingInterceptor()) .addNetworkInterceptor(new Token…

SpringBoot 整合 mybatis-generator 插件

SpringBoot 整合 mybatis-generator 插件 mybatis-generator 插件 mybatis 相关依赖 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.1</version> &…

Java学习笔记——接口

目录接口的定义和特点JDK8版本中接口成员的特点——默认方法和静态方法JDK9版本中接口成员的特点——私有方法类和接口的关系接口的定义和特点 JDK8版本中接口成员的特点——默认方法和静态方法 JDK9版本中接口成员的特点——私有方法 类和接口的关系

JVM 基础 - JVM 内存结构

JVM 内存结构运行时数据区一、程序计数器&#xff08;线程私有&#xff09;二、虚拟机栈&#xff08;线程私有&#xff09;三、本地方法栈&#xff08;线程私有&#xff09;四、堆内存&#xff08;线程共享&#xff09;五、方法区&#xff08;线程共享&#xff09;运行时数据区…

【Git笔记】分支操作与合并分支(正常与冲突)

分支的操作 命令名称作用git branch 分支名创建分支git branch -v查看分支git checkout 分支名切换分支git merge 分支名把指定的分支合并到当前分支上 查看分支 创建分支 切换分支 合并分支&#xff08;正常合并&#xff09; 在 master 下&#xff0c;hello.txt 在 hot-fix …

[Spring Boot]11 使用@Cacheable注解实现Redis缓存

前言 为了方便讲解&#xff0c;模拟一个需要使用Redis缓存的场景&#xff0c;比如&#xff1a;一款APP的首页&#xff0c;由于其需要加载的数据量较大&#xff0c;于是决定把首页的部分数据使用Redis进行缓存&#xff0c;举例&#xff1a;比如要缓存首页的文章列表(ArticleLis…

Cartesi 2023 年 1 月回顾

2023 年1月 31日&#xff0c;准备迎接令人兴奋的一年&#xff0c;你做好准备了吗&#xff1f; 本月我们围绕游戏领域开展了很多推广活动。主持了我们的第二次游戏开发者社区电话会议&#xff0c; Cartesi 大使 Zach和Ultrachess 开发者 Jesse在 ATX Game Makers 的 game jam 上…

34k*16 薪,3年自动化测试历经3轮面试成功拿下华为Offer....

前言 转眼过去&#xff0c;距离读书的时候已经这么久了吗&#xff1f;&#xff0c;从18年5月本科毕业入职了一家小公司&#xff0c;到现在快4年了&#xff0c;前段时间社招想着找一个新的工作&#xff0c;前前后后花了一个多月的时间复习以及面试&#xff0c;前几天拿到了华为…

background-attachment属性值scroll、fixed和local的区别

首先看菜鸟教程中的解释&#xff1a; scroll&#xff1a;背景图片随着页面的滚动而滚动&#xff0c;默认值 fixed&#xff1a;背景图片不会随着页面的滚动而滚动 local&#xff1a;背景图片会随着元素的内容滚动而滚动 代码结合实例说明 先看代码&#xff1a; 主要包含两个盒子…

MyBatis(二)MyBatis入门程序

一、版本 软件版本&#xff1a; IntelliJ IDEA&#xff1a;2022.1.4Navicat for MySQL&#xff1a;16.0.14MySQL数据库&#xff1a;8.0.30 组件版本&#xff1a; MySQL驱动&#xff1a;8.0.30MyBatis&#xff1a;3.5.10JDK&#xff1a;Java17JUnit&#xff1a;4.13.2Logback…

实景三维模型道路中有很多破损车辆,有没有可以一键修除或去掉的办法?

在超大规模实景三维数据生产中&#xff0c;三维模型质量会受到移动物体这类客观因素的影响&#xff0c;常常造成道路模型严重扭曲以及纹理的错位。 1.三维场景重建中的移动车辆问题 车辆作为日常出行重要的交通工具&#xff0c;会出现在城市场景中的各个角落且不断移动。由于…

【方案】契约锁电子签章在50多个行业的详细应用场景

2022年&#xff0c;契约锁电子签章持续深入政府机关、集团、高校、医院、金融、工程、汽车、能源、食品、检测等各行业中大型组织&#xff0c;在与各类管理软件集成应用中&#xff0c;不断丰富签署场景&#xff0c;实现了300多种业务文件电子签署应用&#xff0c;持续助力组织数…

数据结构与算法:优先级队列(堆)

1.优先级队列 1.定义 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然不…

【Rust】16. 智能指针

智能指针&#xff08;smart pointers&#xff09;是一类数据结构&#xff0c;他们的表现类似指针&#xff0c;但是也拥有额外的元数据和功能引用是一类只借用数据的指针&#xff1b;相反&#xff0c;在大部分情况下&#xff0c;智能指针拥有他们指向的数据 16.1 Box<T>&a…

1610_PC汇编语言_整形的表达

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 前面对于汇编有了一个基本的了解&#xff0c;这一章节主要是看一下汇编视角下的数据表达以及计算方式。 1. 整形会有有符号和无符号两种&#xff0c;一般都是用最高…

快上车,程序狗好用的奇淫技巧

文章目录前言&#x1f34a;缘由⏲️本文阅读时长&#x1f3af;主要目标正文&#x1f9d9;‍♂️1.魔术橡皮擦&#x1f415;2.狗屁不通文章生成器&#x1f95e;3.easypdf&#x1f97d;4.Md2All&#x1f32e;5.CSDN开发助手&#x1f468;‍&#x1f4bb;6.猿如意&#x1f9e9;7.P…

Three.js 初阶基础篇(二)

系列文章目录 我今天又来了更新了&#xff01;&#xff01;&#xff01;今天主要还是回顾一下昨天的一内容&#xff0c;在昨天的基础上又重新梳理了一下&#xff0c;创建动态3D正方体的流程&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 文章目录…