登录
/
注册
首页
论坛
其它
首页
科技
业界
安全
程序
广播
Follow
园子
关于
博客
发1篇日志+1圆
记录
发1条记录+2圆币
发帖说明
登录
/
注册
账号
自动登录
找回密码
密码
登录
立即注册
搜索
搜索
关闭
CSDN热搜
程序园
精品问答
技术交流
资源下载
本版
帖子
用户
软件
问答
教程
代码
VIP申请
网盘
联系我们
道具
勋章
任务
设置
我的收藏
退出
腾讯QQ
微信登录
返回列表
首页
›
业界区
›
业界
›
对比分析LinkedBlockingQueue和SynchronousQueue
对比分析LinkedBlockingQueue和SynchronousQueue
[ 复制链接 ]
移国拱
4 天前
缘起
最近在 review 同事代码时,看到其使用了org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor 来构建线程池,而没有使用 Java 类库,部分代码如下:
@Bean
public ThreadPoolTaskExecutor queryToCExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
//线程池维护线程的最少数量
poolTaskExecutor.setCorePoolSize(5);
//线程池维护线程的最大数量
poolTaskExecutor.setMaxPoolSize(32);
//允许的空闲时间,尽量复用,减少创建/销毁操作
poolTaskExecutor.setKeepAliveSeconds(60);
//缓存队列 0:不加入队列
poolTaskExecutor.setQueueCapacity(0);
poolTaskExecutor.setThreadGroupName("xxx");
//阻塞加入队列
poolTaskExecutor.setRejectedExecutionHandler(new QueryRejectedExecutionHandler());
return poolTaskExecutor;
}
复制代码
出于对注释中「缓存队列 0:不加入队列」的好奇,就看了下对应的源码(如下图),发现原来 Spring 会简单地根据容量值是否大于0而选择不同的Java阻塞队列作为其线程池的任务队列:队列容量大于0为 LinkedBlockingQueue,其他情况为 SynchronousQueue。看到这里,正好把我所了解到的有关这两个队列的内容梳理一下,是为温故而知新。
LinkedBlockingQueue面面观
设计目的
:为了消弭生产者和消费者之间的速度差异,提供一个安全的线程间缓存队列。
实现机制
:
LinkedBlockingQueue是基于链表的 FIFO(First In First Out,即先进先出)阻塞队列,即其内部维护了一个单向链表,插入元素时在队列尾部追加节点,删除元素时在队列头部取出节点,以保证FIFO;
对于生产/消费并发控制,内部定义了两个独立的锁:一把用于入队的 putLock,一把用于出队的 takeLock,这种锁分离机制,可以使生产者的入队操作和消费者的出队操作可以并行。
同时,为了协调生产者/消费者,其还配备了对应的条件变量:在队列满时阻塞生产者的 notFull(notFull=putLock.newCondition()),以及在队列空时阻塞消费者的 notEmpty(notEmpty=takeLock.newCondition())。当生产者插入元素使队列从空变为非空时,会 signal notEmpty 通知等待的消费者线程;类似地,当消费者移除元素使队列从满变为未满时,会 signal notFull 通知等待的生产者线程。
容量特性
:
默认为无界队列(容量为Integer.MAX_VALUE),生产者不会因为队列满了而阻塞,实际上仍然受内存限制
有界模式,可指定容量,如 new LinkedBlockingQueue(100);
操作特性
:
支持异步操作,生产者可以独立插入元素(如果队列未满),消费者可以独立取出元素(如果队列非空);
插入/删除时间复杂度为 O(1),但遍历操作(如 contains())时间复杂度为 O(n)。
适用场景
:
固定大小线程池(如Executors.newFixedThreadPool())使用无界的 LinkedBlockingQueue 存放多余任务;
通用生产-消费者模型需要缓冲时;
适合生产消费速率不一致、有突发流量需要缓冲的场景。
其在 JDK 实现的类UML 如下图:
SynchronousQueue面面观
设计目的:提供线程间同步交换数据的机制。
实现机制:
SynchronousQueue底层没有使用传统的数据结构,内部可理解为维护了两个队列/栈结构:一个等待中的生产者线程集合和一个等待中的消费者线程集合。
当有生产者线程执行 put 时,如果此时有消费者线程在等待获取元素,双方直接配对完成元素交接;如果没有消费者等待,那么生产者线程就会自己阻塞并进入等待集合。对消费者线程执行 take 时也是类似的:如有等待中的生产者,它们配对交接;如果没有生产者等待,则消费者线程阻塞进入等待集合。
对于生产/消费并发控制,JDK 底层的实现对上面这种等待线程的管理分为两种模式:非公平模式下使用栈结构后进先出(LIFO)地管理等待线程(内部类称为TransferStack),公平模式下使用队列结构先进先出(FIFO)地管理等待线程(内部类TransferQueue)。
对于协调生产者/消费者,没有像LinkedBlockingQueue使用锁机制,而是采用了
CAS
来管理。
容量特性:容量为0,无法缓存任何元素。
操作特性:
严格同步,生产者和消费者必须成对出现:插入操作(put())必须等待对应删除操作(take()),反之亦然;
不支持迭代和查看元素(如 peek() 永远返回 null)。
适用场景:
缓存线程池(如Executors.newCachedThreadPool())使用SynchronousQueue直接把任务交给线程或创建新线程执行;
需要严格同步交接的场景(比如两个线程交替工作)。
适合生产消费速率相当、要求低延迟无排队的场景
其在 JDK 实现的类UML 如下图:
一表式总结
根据上面的两个类的 UML 图,可以发现两者都实现了相同的接口BlockingQueue,所以都是阻塞队列,在特定条件下都会阻塞线程调用,只是底层实现不相同而已。
对于不相同的地方,下面的表格总结了 LinkedBlockingQueue 和 SynchronousQueue 在各方面的差异:
对比维度LinkedBlockingQueue(LBQ)SynchronousQueue (SQ)
容量
可选有界/无界(默认)的FIFO阻塞队列,基于链表节点存储元素容量为0的阻塞队列,不存储元素,只在线程间直接交换数据
底层结构
链表结构:内部有节点类存放元素,维护头尾指针和计数器。使用两把锁(putLock/takeLock)分别控制入队出队。有条件变量 notFull/notEmpty 用于阻塞等待。无具体数据结构容器。JDK内部通过等待线程队列/栈管理:非公平模式用栈(LIFO),公平模式用队列(FIFO)存放等待的线程节点。通过
CAS
和 LockSupport 挂起/唤醒线程来交换元素。
线程交互
生产者插入操作在队列满时阻塞,消费者移除操作在队列空时阻塞;可以同时有多个元素在队列中等待处理。每次插入操作必须等待有对应的移除操作才能进行,反之亦然。队列中始终不会有多于一个元素存在(实际上最多瞬间有一个正在交接的元素),生产和消费必须配对完成。
性能特点
插入和移除使用独立锁,支持一定程度并行,吞吐量高;在高并发下存在锁竞争和上下文切换,性能可能不够稳定。内有缓冲会增加任务延迟但减少生产者阻塞。采用无锁算法,线程直接配对交换,极低的同步开销,单对线程下吞吐极高;无缓冲减少了排队延迟,但如果一方线程不足会使另一方阻塞等待。大量线程不匹配时可能出现许多线程挂起,极端高并发下吞吐可能下降。公平模式下性能略低于非公平模式,因为要额外开销保证 FIFO。
典型应用
固定大小线程池(如 Executors.newFixedThreadPool())使用无界LBQ存放多余任务;通用生产-消费者模型需要缓冲时。适合生产消费速率不一致、有突发流量需要缓冲的场景。缓存线程池(如 Executors.newCachedThreadPool())使用SQ直接把任务交给线程或创建新线程执行;需要严格同步交接的场景。适合生产消费速率相当、要求低延迟无排队的场景。
公平策略
不支持支持
迭代能力
提供弱一致性迭代不支持迭代
如何快速理解两者的工作原理
为了能够快速理解两者的工作原理,这里以快递送达为比喻进行解释:
LinkedBlockingQueue 像是菜鸟物流,快递员(
生产者
)总是把包裹(
任务
)放到菜鸟驿站(里面有固定数量的储物架,可理解为
任务队列
),收件人(
消费者
)可以在空闲时去菜鸟驿站取件,而不用必须等快递员把包裹送到面前,即强调双方的时间是可以错开的,包裹的送达(
入队
)和领取(
出队
)的动作是可以异步进行的。
SynchronousQueue 就像是闪送,快递员(
生产者
)必须把包裹(
任务
)当面交给收件人(
消费者
),因没有储物架(
没有任务队列
)而不能提前送达:如果收件人没有来,快递员则会一直等待收件人出现,同理,收件人也只能等待快递员出现才能当面领到包裹,即强调双方必须同时在场。
以上。如有错误疏漏,欢迎评论一起探讨!
如果你觉得我的工作对你有帮助,可以通过分享和推荐这篇文字或者关注同名公众号来支持我,你的支持是我持续创作的动力:
转载以及引用请注明原文链接
。
本博客所有文章除特别声明外,均采用CC 署名-非商业使用-相同方式共享 许可协议。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复
使用道具
举报
提升卡
置顶卡
沉默卡
喧嚣卡
变色卡
千斤顶
照妖镜
相关推荐
那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程
如何优雅的使用RabbitMQ
分布式锁1 Java常用技术方案
浅谈我对DDD领域驱动设计的理解
游戏编程十年总结(下)
【前端性能】高性能滚动 scroll 及页面渲染优化
验证码对抗之路及现有验证机制介绍
从零开始入门 K8s | 手把手带你理解 etcd
中文写程序,何陋之有?
NHibernate之旅(2):第一个NHibernate程序
公司的中场
FFmpeg开发笔记(六十二)Windows给FFmpeg集成H.266编码器vvenc
谈谈如何从本质上理解sql语句, 存储过程,ORM之间的联系和取舍。
Android 系统缺陷不完全点评
[一步一步MVC]第一回:使用ActionSelector控制Action的选择
.net环境下跨进程、高频率读写数据
第二个iPhone应用程序:“Say Hello”
关于编程的胡扯
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
回复
本版积分规则
回帖并转播
回帖后跳转到最后一页
浏览过的版块
科技
安全
签约作者
程序园优秀签约作者
发帖
移国拱
4 天前
关注
0
粉丝关注
10
主题发布
板块介绍填写区域,请于后台编辑
财富榜{圆}
敖可
9986
森萌黠
9994
里豳朝
9994
4
柴古香
9994
5
背竽
9994
6
猷咎
9992
7
凶契帽
9992
8
黎瑞芝
9992
9
松菊
9992
10
尝琨
9992
查看更多