1、LinkedBlockingQueue概述

  Linked:链表+Blocking:阻塞+Queue:队列

  Queue:首先想到的是FIFO

  Linked:,Queue:其结构本质上是线性表,可以有链表和顺序表实现

  LinkedBlockingQueue就是链表实现

  ArrayBlockingQueue就是顺序表实现,因为Queue只在尾部操作,所以操作顺序表和链表的时间复杂度是一样的,但顺序表的实现占用更小的空间,因为不需要指针域,但是空间必须是连续的。而链表不需要连续的空间,但需要next来指向下一个结点

  

  Blocking:阻塞,LinkedBlockingQueue是线程安全的,当队列满以后,所有的入队操作都会被阻塞,当队列空的时候,所有的出队都将被阻塞。队列初始化时我们可以初始化长度Capacity.如果没有指定长度,默认是Integer.Max_Value.显然Capacity是一个不可改变的值

  

2、LinkedBlockingQueue源码详解

  如果要看懂LinkedBlockingQueue的源码必须连接Wai/Notify以及AbstractQueuedSychronizer(AQS),个人认为并发编程必须要掌握的三个知识。等待通知机制、CAS、AQS

  2.1 Head和Last分别表示队列的头尾,可快速定位take和put操作位置。注意头结点head和首结点First的区别

  

  2.2 Count表示当前队列中元素的个数,其使用并发包下面的AtomicInteger类来实现原子操作,该类的核心操作还是CAS, AtomicInteger对于队列的线程安全有着至关重要的作用,因为接下来要看take和put是两个相对独立的锁

  2.3、有兴趣的话可以看看ArrayBlockingQueue,其中只使用了一个锁来保证线程安全,所以它的Count没有使用AtomicInteger,而是一个int类型

  

  2.3、锁与条件

    TakeLock和PutLock分别定义了take和put操作的锁

    take操作的条件是notEmpty,所以在执行take操作的时候,要先判断当前队列是否还有元素可以take.如果没有那么就要执行notEmpty.await()方法让take线程阻塞

    put操作的条件是notFull,所以在执行put操作的时候,会先判断当前队列是否还有空间可以put操作。如果没有空间那么put制作执行notFull.await()方法

     成功take以后,会判断take之前队列是不是满的,如果是可能会有put线程被阻塞了,所以会调用singnalNotFull()方法去唤醒那些阻塞的put线程

        成功put以后会判断在put之前队列是不是空的,如果是,说明可能会有take线程是阻塞的,所以会调用SignalNotEmpty()去唤醒那些take线程

       前两部设计的相当好,先判断Empty或Full,然后再去调用唤醒方法。避免无谓的唤醒操作

    

  2.4:LinkedBlockingQueue构造方法

  

  put操作

  1、在尾部插入一个指定的元素e,如果没有空间,线程将会等待

    2、e不允许为空,该队列不存储null元素,否则抛出NullPointException()异常

  3、局部变量c初始值是-1,其存储当前队列的元素个数,准确的说是put之前的操作个数,因为c=count.getAndIncrent(),而getAndIncrentment()返回的是previous()值(c的值很重要,不然无法理解唤醒的操作) 

  4、putLock.Interruptily()获取锁

  5、如果count.get()==Capacity.说明队列已经没有剩余空间了,那么条件为NotFull的操作,put操作线程要执行语句notFull.await()进入等待。否则正常入队

  6、正常入队后,count+1,c获取的是入队前的值(这点需要注意)

  7、c+1 < capacity,表示当前队列的个数小于capacity,那么就可以唤醒一下那些条件为not Empty的put操作线程(当日此时不一定有等待线程)

  8、如果c==0,即put之前队列是空的,那么就有可能take操作线程在等待,所以执行signalNotEmpty(),该方法先获取take锁,然后唤醒等待take线程来take

  

  1、|take和put原理上是相同的,take是从first节点开始出队,注意区分head;如果队列中没有节点,那么take线程就需要等待

  2、局部变量c初始值为-1,其存储当前队列的元素个数,准确地说是take操作之前的元素个数

  3、takeLock.lockInterruptibly() 获取take lock

  4、count.get() == 0,如果当前队列中没有元素,那么条件为not Empty 的take操作线程将要等待;否则正常出队

  5、c > 1 表示take以前队列中至少是有2个元素,那么可以唤醒其它在等待的take线程,操作为notEmpty.signal()

  6、c == capacity 表示take操作前队列是满的,那么就有可能有put线程在等待着,因此执行signalNotFull(),该方法首先获取put锁,然后唤醒那些可能在等待的put线程

  

  1、重载的两个offer方法本质上也是put操作,但在操作上略有不同。

  2、一个offer方法提供了线程等待时间,其先进入条件的等待队列等待

  3、另一个offer方法是能入队就入队,不能就返回false,不等了

  4、这两种offer方法可根据实际需要来适当选择

poll与peek

  1、两个poll方法也是take的改版,一个是超时等待,一个干脆就不等了,有就取,没有就算了。两种方法可在实际应用中按需选用

  2、peek方法和take方法不同的是没有出队,只是"看看"首元素first.item

总结

  1、LinkedBlockingQueue体现了生产者/消费者模型,借助wait/notify机制,可实现take、put操作线程的等待与唤醒

  2、AtomicInteger类型的count(队列中当前元素个数)以及双锁机制(take和put锁)共同使得LinkedBlockingQueue是线程安全的。实现方式值得学习和体会

  3、

一行一行读Java源码——LinkedBlockingQueue的更多相关文章

  1. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  2. 如何阅读Java源码

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...

  3. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  4. 如何阅读Java源码?

    阅读本文大概需要 3.6 分钟. 阅读Java源码的前提条件: 1.技术基础 在阅读源码之前,我们要有一定程度的技术基础的支持. 假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃< ...

  5. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  6. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  7. 从Java源码到Java字节码

    Java最主流的源码编译器,javac,基本上不对代码做优化,只会做少量由Java语言规范要求或推荐的优化:也不做任何混淆,包括名字混淆或控制流混淆这些都不做.这使得javac生成的代码能很好的维持与 ...

  8. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  9. 跟我一起读postgresql源码(十)——Executor(查询执行模块之——Scan节点(下))

    接前文跟我一起读postgresql源码(九)--Executor(查询执行模块之--Scan节点(上)) ,本篇把剩下的七个Scan节点结束掉. T_SubqueryScanState, T_Fun ...

随机推荐

  1. noip第28课作业

    分段数列 [问题描述] 对于给定的一个长度为N的正整数数列A[i],现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求. 输入格式: 输入第1行包含两个正整 ...

  2. 比较两个Excle表格的修改内容

    //输入参数为文件输入流public static Map<String, List<String>> excelColumn2maplist(InputStream is){ ...

  3. hdu 5091 给定矩形覆盖尽量多点 扫描线+线段树

    http://acm.hdu.edu.cn/showproblem.php?pid=5091 给你10000以内的敌舰的坐标(即分别为x,y),要求用W*H的矩形去围住一个区域,使得这个区域内的敌舰最 ...

  4. ES版本控制

    版本控制 ElasticSearch采用了乐观锁来保证数据的一致性,也就是说,当用户对document进行操作时,并不需要对该doucument作加锁和解锁的操作,只需要指定要操作的版本即可.当版本号 ...

  5. day08_雷神_模块一

    # day08 一.模块 回顾相关: 取值顺序: 在局部调用:局部命名空间->全局命名空间->内置命名空间 在全局调用:全局命名空间->内置命名空间 内置函数: globals(), ...

  6. PL/SQL客户端连Oracle很快就断开问题的解决

    PL/SQL登录很短时间session就自动断开 1.首先查看你这个用户的profile文件 select profile from dba_users where username='USERNAM ...

  7. 3.Django视图

    视图 视图接受Web请求并且返回Web响应 视图就是一个python函数,被定义在views.py中 响应可以是一张网页的HTML内容,一个重定向,一个404错误等等 响应处理过程如下图: URLco ...

  8. Linux下可视化空间分析工具ncdu

    场景:磁盘空间占满后快速查找某个目录(子目录)占用空间大. ncdu /var (分析后按左右键查看即可)

  9. Windows核心编程:第3章 内核对象

    Github https://github.com/gongluck/Windows-Core-Program.git //第3章 内核对象.cpp: 定义应用程序的入口点. // #include ...

  10. oracle查询语句查询增加一列内容

    select a,sys_guid() as b from mytable sys_guid() 是生成带分隔符(-)的GUID的自定义函数 查询B表的内容插入A表,MY_ID是A表的主键不可为空,因 ...