学习BlockingQueue之LinkedBlockingQueue实现原理

 

一:概念

  LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。

与ArrayBlockingQueue的异同:

ArrayBlockingQueue:   必须设置长度容量      底层数组结构           单锁控制

LinkedBlockingQueue:默认Integer最大值       底层链表结构           双锁

二:LinkedBlockingQueue源码实现

不设置容量,默认为Integer的最大值

也支持设置容量

也支持预先将集合设置入队列

两把锁,一个take锁,控制消费者并发,一个put锁,控制生产者并发:

内部维护单向链表结构:

来看一下主要方法:offer与poll

offer方法:

如果e为null或者对列已满,返回false, 然后加锁,其他的生产者会被阻塞,再次判断如果对列里面元素数量小于容量,那么入队,对列的数量也自加,

如果这时对列仍然有空间,会唤醒正在等待的其他生产者,向对列里面放数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean offer(E e) {
       if (e == nullthrow new NullPointerException();
       final AtomicInteger count = this.count;
       if (count.get() == capacity)
           return false;
       int c = -1;
       Node<E> node = new Node<E>(e);
       final ReentrantLock putLock = this.putLock;
       putLock.lock();
       try {
           if (count.get() < capacity) {
               enqueue(node);
               c = count.getAndIncrement();
               if (c + 1 < capacity)
                   notFull.signal();
           }
       finally {
           putLock.unlock();
       }
       if (c == 0)
           signalNotEmpty();
       return c >= 0;
   }

  

入队方法:

如果是第一次放入数据,效果图:

主要是建立两个连接,让最后一个元素last指向新来的元素,然后将last指针指向新来的。

再来看一下poll方法:取数据

如果对列为空,返回null ,然后加锁,其他想取数据的消费者线程会被阻塞, 如果没有数据释放锁,返回null,对列有数据,则出队,对列自减,

如果出队后对列中还有数据,那么会唤醒正在等待的其他消费者线程来取数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public E poll() {
       final AtomicInteger count = this.count;
       if (count.get() == 0)
           return null;
       E x = null;
       int c = -1;
       final ReentrantLock takeLock = this.takeLock;
       takeLock.lock();
       try {
           if (count.get() > 0) {
               x = dequeue();
               c = count.getAndDecrement();
               if (c > 1)
                   notEmpty.signal();
           }
       finally {
           takeLock.unlock();
       }
       if (c == capacity)
           signalNotFull();
       return x;
   }

  

出队方法:

返回first的item元素,这个链表的头结点维护的都是空节点,效果图如下:

出队前:

出队后:

add 和remove:

add方法: 直接使用父类AbstractQueue的方法:

在offer的基础上进行了保证,成功返回true,false的时候返回异常。

remove方法:

两把锁同时上锁,两把锁同时解锁:

来看一下删除元素的动作:因为数据结构是链表,所以只需要把指向该节点的上一个节点的next变量不指向该节点即可,然后

gc的时候就会把该节点回收掉:

trial.next = p.next 的作用就是让p节点的前一个元素直接指向p的后一个元素,而数组结构就是把该下标置为null  object[takeIndex] == null

put和take方法:

put方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void put(E e) throws InterruptedException {
       if (e == nullthrow new NullPointerException();
       // Note: convention in all put/take/etc is to preset local var
       // holding count negative to indicate failure unless set.
       int c = -1;
       Node<E> node = new Node<E>(e);
       final ReentrantLock putLock = this.putLock;
       final AtomicInteger count = this.count;
       putLock.lockInterruptibly();
       try {
           /*
            * Note that count is used in wait guard even though it is
            * not protected by lock. This works because count can
            * only decrease at this point (all other puts are shut
            * out by lock), and we (or some other waiting put) are
            * signalled if it ever changes from capacity. Similarly
            * for all other uses of count in other wait guards.
            */
           while (count.get() == capacity) {
               notFull.await();
           }
           enqueue(node);
           c = count.getAndIncrement();
           if (c + 1 < capacity)
               notFull.signal();
       finally {
           putLock.unlock();
       }
       if (c == 0)
           signalNotEmpty();
   }

  

take方法:

take方法的判断逻辑与poll基本相同,唯一区别是,如果对列没有元素,take为阻塞消费者线程,而poll会返回false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

  

 

深入LinkedBlockingQueue实现原理的更多相关文章

  1. 20.并发容器之ArrayBlockingQueue和LinkedBlockingQueue实现原理详解

    1. ArrayBlockingQueue简介 在多线程编程过程中,为了业务解耦和架构设计,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作.例如在“生产 ...

  2. 理解线程池到走进dubbo源码

    引言 合理利用线程池能够带来三个好处. ​ 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. ​ 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. ...

  3. 学习笔记 07 --- JUC集合

    学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...

  4. BlockingQueue 解析

    阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列 ...

  5. BlockingQueue深入解析-BlockingQueue看这一篇就够了

    本篇将详细介绍BlockingQueue,以下是涉及的主要内容: BlockingQueue的核心方法 阻塞队列的成员的概要介绍 详细介绍DelayQueue.ArrayBlockingQueue.L ...

  6. 008 BlockingQueue理解

    原文https://www.cnblogs.com/WangHaiMing/p/8798709.html 本篇将详细介绍BlockingQueue,以下是涉及的主要内容: BlockingQueue的 ...

  7. 生产者消费者模型Java实现

    生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...

  8. LinkedBlockingQueue出入队实现原理

    类图概述 由类图可以看出,L是单向链表实现的,有两个ReentrantLock实例用来控制元素入队和出队的原子性,takeLock用来控制只有一个线程可以从队头获取元素,putLock控制只有一个线程 ...

  9. 分布式开放消息系统(RocketMQ)的原理与实践

    分布式消息系统作为实现分布式系统可扩展.可伸缩性的关键组件,需要具有高吞吐量.高可用等特点.而谈到消息系统的设计,就回避不了两个问题: 消息的顺序问题 消息的重复问题 RocketMQ作为阿里开源的一 ...

  10. Java并发集合的实现原理

    本文简要介绍Java并发编程方面常用的类和集合,并介绍下其实现原理. AtomicInteger 可以用原子方式更新int值.类 AtomicBoolean.AtomicInteger.AtomicL ...

随机推荐

  1. c语言里关于本地变量的一些规则

    关于块的定义(自己的理解):就是☞{ }这个区域里面的东西以及" {} "这个符号的本身 ·本地的变量是定义在块内的 -->>1.它可以定义在函数的块内 void sw ...

  2. OpenAI使用AI编程给出了数数问题的解决方案 —— 如何解决ChatGPT不会数数的问题

    总所周知的一个问题,那就是ChatGPT不会数数,不过今天突然发现OpenAI给出了一个神奇的解决方法,那就是AI编程. 问题案例如下: The text provided will be analy ...

  3. KVM的基本使用

    1. 虚拟化介绍 虚拟化是云计算的基础.简单的说,虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的 CPU.内存.IO 硬件资源,但逻辑上虚拟机之间是相互隔离的. 物理机我们一般称为 ...

  4. pycharm生成的allure测试报告如何查看本地的index.html文件?

    pycharm生成的allure测试报告应该是通过服务启动查看,但是如果把这个文件保存到本地查看,直接打开页面无内容 可以使用allure-combine工具实现本地正常打开 `from allure ...

  5. 1、oracle实例、软件、库简单讲解

    oracle的基本结构 oracle软件(RDBMS) oracle软件:关系型数据库管理系统 在linux系统上,oracle软件安装在:/u01/app/oracle这个目录下 oracle数据库 ...

  6. linux 自动输入密码脚本避免密码确认

    有时候需要执行一个小脚本,就把一部分命令集合起来,我们可以使用 && 或者 .sh 脚本换行. 而有些时候涉及权限需要输入密码就出现了一些客户端会卡在输密码的界面让用户输入 脚本会暂停 ...

  7. Nuxt.js 应用中的 webpack:change 事件钩子

    title: Nuxt.js 应用中的 webpack:change 事件钩子 date: 2024/11/24 updated: 2024/11/24 author: cmdragon excerp ...

  8. gitlab之配置文件.gitlab-ci.yml

    自动化部署給我们带来的好处 自动化部署的好处体现在几个方面 1.提高前端的开发效率和开发测试之间的协调效率 Before 如果按照传统的流程,在项目上线前的测试阶段,前端同学修复bug之后,要手动把代 ...

  9. 一款开源、免费、美观的 Avalonia UI 原生控件库 - Semi Avalonia

    前言 最近发现DotNetGuide技术社区交流群有不少小伙伴在学习Avalonia,今天大姚给大家分享一款开源.免费.美观的 Avalonia UI 原生控件库:Semi Avalonia. Ava ...

  10. Pro更改启动界面

    该方法适用于arcgispro 3.1及以上版本,我目前测试到3.3,是可以的. 使用的是pro产品的启动配置文件,利用其中的SplashScreen实现这一需求. 在bin目录下,新建(或编辑)Ar ...