Java中的阻塞队列

Java中的阻塞队列

ArrayBlockingQueeue,LinkedBlockingQueue,PriorityBlockingQueue,ConcurrentLinkedQueue,SynchronousQueue,DelayQueue 简单对比

ArrayBlockingQueue

特性

  • 数组存储
  • 有界
  • 阻塞队列
  • FIFO

初始化参数

  • capacity 队列最大可用容量
  • fair 阻塞用的锁是否使用公平锁, 默认为false
  • collection 队列初始化完成后,将collection中的元素放入队列中

基本原理

  1. 使用ReentrantLock保证同一时刻只有一个线程对队列进行读或者写
  2. 使用两个Condition分别来控制生产者和消费者是否进行阻塞,以及是否取消阻塞
  3. 容量在创建时即初始化好,并分配空间,不可改变
  4. 内部维护三个变量takeIndex(尾索引),putIndex(头索引), count(实际大小),因为只有一把锁,所以count不需要特殊修饰符修饰

LinkedBlockingQueue

特性

  • 链表存储
  • 可设置边界
  • 阻塞队列
  • FIFO

初始化参数

  • capacity 队列的最大容量,默认为:Integer.MAX_VALUE
  • collection 队列初始化完成后,将collection中的元素放入队列中

基本原理

  1. 使用ReentrantLock分别创建了两把锁,读锁和写锁
  2. 通过读锁和写锁各自的信号量单独控制生产者和消费者是否阻塞
  3. 只设置最大容量,不预先分配空间
  4. 内部维护head(头指针),last(尾指针),以及AtomicInteger类型的count(实际大小),因为是两把锁,所以count需要在能够在不同线程间通讯。

LinkedBlockingQueue与ArrayBlockingQueue对比

这两个队列的区别主要是因为存储数据的结构不同引起的.

1. array和LinkedList数据结构不同导致Queue处理capacity参数的策略不同

  • array初始化时,必须设置大小,并且初始化,所以ArrayBlockingQueue必须初始化容量,并且当容量设置过大时, 在初始化时,就有内存溢出的风险
  • 而链表初始化时,仅仅初始化了一个头尾指针,所以LinkedBlockingQueue的容量大小可以设置为Integer.MAX_VALUE。

2. array和LinkedList数据结构不同导致Queue内部设计锁的策略不同

从array和linkedList都是可以分别设置读写锁的,那为什么ArrayBlockingQueue公用一把锁呢?

  1. ArrayBlockingQueue设置读锁和写锁,就需要考虑count就需要使用AtomicInteger,而array在操作元素的时候,只需要更新数组的指针指向,读写速度相对一致,此时AtomicInter类型的count有可能成为性能瓶颈(乐观锁遇见同时修改数据的情况,性能较低),所以不如直接使用一把锁,性能基本相似,并且代码复杂度降低
  2. 当Queue的容量为空的时候,此时读锁和写锁需要控制array的同一个位置,此时的性能和使用一把锁是一样的。而且从实际场景来看,我们无法判断这种情形的多少,倒不如减少代码复杂度,
  3. linkedList因为是使用的链表,即使队列为空时,其也是分别操作头尾指针,所以采用读锁和写锁可以降低抢占锁的性能
  4. linkedList 插入数据时,需要先构建node,而取数据时,只需要分离指针即可,读写操作耗时不一致,相当于降低了同时修改cout值的概率,此时乐观锁效率较高

3. 为什么通常说 LinkedBlockingQueue比ArrayBlockingQueue速度快,但是LinkedBlockingQueue的性能却是不可预测或者说不稳定的呢?

快是因为双锁比单锁要快,不稳定是因为乐观锁,乐观锁在并发过高的时候性能不稳定。

PriorityBlockingQueue

特性

  • 数组存储
  • 无界
  • 阻塞队列
  • 根据执行策略进行优先级排序出列

初始化参数

  • initialCapacity 初始容量,默认为11,设定初始容量可以在能够预估容量的情况下,避免因为多次扩容造成的性能浪费
  • comparator 排序规则,默认采用自然排序
  • collection 将collection中的元素放入队列中

基本原理

  1. 使用二叉树最小堆算法来维护内部数组
  2. 使用ReentrantLock保证同一时刻只有一个线程对队列进行读或者写
  3. 使用一个Condition控制消费者是否进行阻塞,以及是否取消阻塞
  4. 容量可动态扩容
  5. 插入数据时,根据预设排序规则,将元素插入指定位置,
  6. 取数据时,从队列头部取,因为头部的数据优先级总是最高的

QA

  1. Q: 为什么不用没有使用链表实现的优先级队列?
    A: 因为优先级队列涉及集合的排序过程,而大多数排序算法都需要随机插入,在随机插入方面数组的效率是远优于链表的。所以使用数组而不是链表。
  2. Q: PriorityBlockingQueue最大容量为什么是Integer.MAX_VALUE - 8?
    A: 作者的解释是:有些jvm会在数组放一下头信息,为了避免内存溢出,所以设置最大容量为Integer.MAX_VALUE - 8, 在Java中,几乎所有以数组作为存储结构的工具类都是这么设计的。比如ArrayList。那为什么要减8呢?因为jvm的header信息在32位的系统下是4字节,在64位系统下是8字节。所以预留减8足够了。

ConcurrentLinkedQueue

特性

  • 链表存储
  • 无界
  • 并发安全
  • FIFO

基本原理

ConcurrentLinkedQueue是基于乐观锁(CAS)实现的并发安全的无界队列。其使用乐观锁存取数据,避免了在等待锁时,因线程挂起导致上下文切换所耗费的性能。
在引入乐观锁的同时, ConcurrentLinkedQueue引入惰性更新的逻辑,延迟更新tail节点的更新频率。即,正常操作应该是每次存取数据都要更新tail节点,但是ConcurrentLinkedQueue只有在tail的操作次数达到阈值后才会被更新,这样就减少了锁的次数。降低性能开销。

LinkedTransferQueue

特性

  • 链表存储
  • 无界
  • 并发安全
  • FIFO
  • 阻塞

基本原理

LinkedTransferQueue扩展了内部存储的数据类型,即 懒惰双类型队列(Dual Queues with Slack),一般的队列,内部只是缓存生产者产生的数据,这种情况下,从生产者到消费者,数据流转必须经历一个完整的入队,出队过程,即使此时消费者的能力是大于等于生产者的。
而LinkedTransferQueue不仅可以缓存生产者产生的数据,还可以缓存消费者线程。通过这种方式,将数据交换变为:插入数据时,判断有没有消费者在等待,若有则直接将数据交给消费者,没有这加入队列缓存。而消费者在取数据时,若发现队列为空,此时是将自己缓存到队列的尾节点中。通过这种设计,一方面减少了消费者能力大于等于生产者条件下的数据交换逻辑,同时减少了锁的开销。
LinkedTransferQueue在更新tail节点时,也引入惰性删除的逻辑。

SynchronousQueue

特性

  • 阻塞队列
  • 零容量
  • 阻塞
  • FIFO

初始化参数

  • fail 是否使用公平锁,默认为false

基本原理

SynchronousQueue分公平模式和非公平模式。

  • 公平模式内部实现了一个TransferQueue,该队列的特点是不存储数据,然后将提交数据的生产者和消费数据的消费者阻塞起来,当生产者线程和消费者线程同时出现时,即可直接交换数据。 TransferQueue内部实现了一个链表,用于存储生产者和消费者线程。当没有成对的生产者消费者出现时,所有来存取数据的线程都会被阻塞在链表中。直到有匹配线程出现,则按照入队顺序唤醒相应的线程,然后交换数据。

  • 非公平模式内部实现了一个TransferStack, 即使用栈的先进后出特性实现非公平性。

DelayQueue

特性

  • 延时
  • 阻塞
  • 无界

基本原理

DelayQueue内部使用PriorityQueue实现了一个针对数据缓存时间的优先级队列。所以其特性同PriorityBlockingQueue是相同的。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • 移步java多线程系列文章 1 什么是阻塞队列 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列...
    凯玲之恋阅读 723评论 0 4
  • 本文摘自《Java并发编程的艺术-方腾飞》 本节将介绍什么是阻塞队列,以及Java中阻塞队列的4种四种处理方式,并...
    Briarbear阅读 423评论 0 1
  • 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线...
    端木轩阅读 1,002评论 0 2
  • 队列(Queue):FIFO 双端队列(Deque):两端都可以进出,当我们约束从队列的一端进出队列时,就形成了一...
    kevin0016阅读 489评论 0 0
  • Java中的阻塞队列 1. 什么是阻塞队列 阻塞队列是支持两个附加操作的队列。这两个附加操作就是阻塞式的插入和移除...
    王小冬阅读 343评论 0 0