JUC之BlockingQueue

常见的 BlockingQueue

9.4.1 ArrayBlockingQueue(常用)

基于数组 的阻塞队列实现,在 ArrayBlockingQueue 内部,维护了一个定长

组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数

组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的

头部和尾部在数组中的位置。

ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个

锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于

LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue 完全可

以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea 之

所以没这样去做,也许是因为 ArrayBlockingQueue 的数据写入和获取操作已

经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其

在性能上完全占不到任何便宜。 ArrayBlockingQueue 和

LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除

元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的

Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于

GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还

可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

一句话总结: 由数组结构组成的有界阻塞队列。

9.4.2 LinkedBlockingQueue(常用)

基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,其内部也维持着一

个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据

时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;

只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue 可以通过

构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生

产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发

的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列

的并发性能。

ArrayBlockingQueue 和 LinkedBlockingQueue 是两个最普通也是最常用

的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个

类足以。

一句话总结: 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。

9.4.3 DelayQueue

DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到

该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的

操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻

塞。

一句话总结: 使用优先级队列实现的延迟无界阻塞队列。

9.4.4 PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来

决定),但需要注意的是 PriorityBlockingQueue 并不会阻塞数据生产者,而

只会在没有可消费的数据时,阻塞数据的消费者

因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费

数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。

在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁

一句话总结: 支持优先级排序的无界阻塞队列。

9.4.5 SynchronousQueue一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产

者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须

亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么

对不起,大家都在集市等待。相对于有缓冲的 BlockingQueue 来说,少了一

个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经

销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以

库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式

会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得

产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能

可能会降低。

声明一个 SynchronousQueue 有两种不同的方式,它们之间有着不太一样的

行为。

公平模式和非公平模式的区别:

• 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞

多余的生产者和消费者,从而体系整体的公平策略;

• 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平

锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者,而后一种模式,

如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有

某些生产者或者是消费者的数据永远都得不到处理。

一句话总结: 不存储元素的阻塞队列,也即单个元素的队列。

9.4.6 LinkedTransferQueue

LinkedTransferQueue 是一个由链表结构组成的无界阻塞 TransferQueue 队

列。相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和

transfer 方法。

LinkedTransferQueue 采用一种预占模式。意思就是消费者线程取元素时,如

果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素

为 null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时

发现有一个元素为 null 的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的

方法返回。

一句话总结: 由链表组成的无界阻塞队列。

9.4.7 LinkedBlockingDeque

LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列,即可以从队

列的两端插入和移除元素。

对于一些指定的操作,在插入或者获取队列元素时如果队列状态不允许该操作

可能会阻塞住该线程直到队列状态变更为允许操作,这里的阻塞一般有两种情

• 插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时

再讲该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作

失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException 异

• 读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可

以通过设置超时参数

一句话总结: 由链表组成的双向阻塞队列

9.5 小结

1. 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件

满足,被挂起的线程又会自动被唤起

2. 为什么需要 BlockingQueue? 在 concurrent 包发布以前,在多线程环境下,

我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,

而这会给我们的程序带来不小的复杂度。使用后我们不需要关心什么时候需要

阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手

包办了.

相关推荐
Mr.Java.28 分钟前
Spring Boot MongoDB自定义连接池配置
java·spring boot·后端·mongodb
JIngJaneIL42 分钟前
健身管理小程序|基于java微信开发健身管理小程序的系统设计与实现(源码+数据库+文档)
java·数据库·小程序·vue·毕业设计·论文·健身管理小程序
菠萝崽.1 小时前
springboot中测试python脚本:ProcessBuilder
java·开发语言·spring boot·python·processbuilder
哪吒编程1 小时前
从0.031秒优化0.018秒,JEP 483为Java应用带来的启动加速黑科技
java·后端
努力的搬砖人.1 小时前
nacos配置达梦数据库驱动源代码步骤
java·服务器·数据库·经验分享·后端
朱啸毅1 小时前
Tomcat
java·tomcat
风象南2 小时前
SpringBoot中3种条件装配技术
java·spring boot·后端
Java小白中的菜鸟2 小时前
深入理解Java反射
java·开发语言
自由与自然2 小时前
乐观锁与悲观锁的使用场景
java·服务器·数据库
爱的叹息5 小时前
spring mvc 中 RestTemplate 全面详解及示例
java·spring·mvc