学习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. 鸿蒙NEXT开发案例:年龄计算

    ​ [引言] 本案例的目标是开发一款年龄计算器应用,该应用能够根据用户输入的出生日期,计算出用户的实际年龄.虚岁.星座.生肖等信息.同时,应用还将提供距离下次公历和农历生日的天数及星期等信息.为了实现 ...

  2. API13Bate版来了DevEco已更新快来看新功能吧

    HarmonyOS 5.0.1 Beta3,是HarmonyOS开发套件基于API 13正式发布的首个Beta版本.该版本在OS能力上主要增强了C API的相关能力,多个特性补充了C API供开发者使 ...

  3. CF207C3 Game with Two Trees

    CF207C3 Game with Two Trees 妙到家的树上字符串问题. 约定 树 \(1\):\(t_1\). 树 \(2\):\(t_2\). \(S_{1/2}(i,l)\) 为树 \( ...

  4. BeanStalkd 做队列服务

    今天无意间看到这个仓库讲php关于 BeanStalkd 的扩展,然后就去了解了一下beanstalkd,才知道它可以用来做队列服务. 话不多说,安装一下试试. 首先 sudo apt search ...

  5. 从零开始:NetBox 4.1 Docker 部署和升级

    前言 由于Netbox 官方的中文语言日渐完善,所以新出一个使用官方Docker源部署和升级的教程. Netbox 系列文章:https://songxwn.com/categories/NetBox ...

  6. 通过squid将本地作为代理让不可联网的远端服务器联网

    一种方法 https://unix.stackexchange.com/questions/116191/give-server-access-to-internet-via-client-conne ...

  7. Tomcat并发数优化的方法总结

    web应用的并发提升,除了负载均衡.在小企业中也可以通过一些软件的上的设置来进行一些优化.下面是一些在服务器上修改tomcat参数的优化方法,非常简单实用!(这些方法通过网络整理的) 1,让Tomca ...

  8. golang之热加载Fresh&air

    Fresh 是一个命令行工具,每次保存Go或模版文件时,该工具都会生成或重新启动Web应用程序.Fresh将监视文件事件,并且每次创建/修改/删除文件时,Fresh都会生成并重新启动应用程序.如果go ...

  9. elasticsearch-head插件基本使用

    1. 查看搜索setting信息 mp_index/_settings 2. 设置分片数量 3, 修改数据刷新间隔 { "refresh_interval": "30s& ...

  10. linux 安装 docker

    1.安装 yum-utils yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 --skip-broken 执行如果 报错 ...