SynchronousQueue 与 LinkedBlockingQueue区别及应用场景

文章目录

  • 前言
  • 认识SynchronousQueue
  • 基本对比及比较
    • 1. **基本特性**
    • 2. **内部实现**
    • 3. **性能特点**
    • 4. **使用场景**
    • 5. **总结对比**
  • SynchronousQueue案例
    • JDK应用案例
    • 案例1:SynchronousQueue的简单用例
    • 案例2:SynchronousQueue公平锁、非公平锁案例
    • 案例3:搭配线程池使用
      • 线程池多线程+SynchronousQueue(公平无效)
      • 线程池多线程+SynchronousQueue(公平有效)
  • 资料获取

稿定智能设计202502032102

前言

博主介绍:✌目前全网粉丝3W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

视频平台:b站-Coder长路

本章节配套源码:

  • gitee:https://gitee.com/changluJava/demo-exer/tree/master/JUC/src/main/java/demo11

认识SynchronousQueue

SynchronousQueue 是一个特殊的队列,它的核心特点是 不存储元素,每个插入操作必须等待一个对应的移除操作,反之亦然。这种特性使得它非常适合一些特定的应用场景。

这种机制非常适合处理大量短期异步任务的场景,例如 Web 服务器处理请求、短时计算任务等。

SynchronousQueueLinkedBlockingQueue 是 Java 并发包中两种不同的队列实现,它们的设计目标和使用场景有显著区别。以下是它们的主要区别。

关于SynchronousQueue的公平与非公平体现在多线程场景情况

  • 公平:多个线程进行put阻塞的话,按照谁先进行put会优先去执行任务。(底层是队列)
  • 非公平:多个线程进行put阻塞的话,并非是排队的,而是会以栈的结构先进后出。(底层是栈)

关于公平非公平源码解析:Java阻塞队列中的异类,SynchronousQueue底层实现原理剖析

BlockingQueue接口说明

BlockingQueue设计了很多的放数据和取数据的方法

操作抛出异常返回特定值阻塞阻塞一段时间
放数据addofferputoffer(e, time, unit)
取数据removepolltakepoll(time, unit)
查看数据(不删除)element()peek()不支持不支持

这几组方法的不同之处就是:

  1. 当队列满了,再往队列中放数据,add方法抛异常,offer方法返回false,put方法会一直阻塞(直到有其他线程从队列中取走数据),offer(e, time, unit)方法阻塞指定时间然后返回false。
  2. 当队列是空,再从队列中取数据,remove方法抛异常,poll方法返回null,take方法会一直阻塞(直到有其他线程往队列中放数据),poll(time, unit)方法阻塞指定时间然后返回null。
  3. 当队列是空,再去队列中查看数据(并不删除数据),element方法抛异常,peek方法返回null。

工作中使用最多的就是offer、poll阻塞指定时间的方法。


基本对比及比较

1. 基本特性

特性SynchronousQueueLinkedBlockingQueue
容量容量为 0,不存储元素。容量可配置(默认 Integer.MAX_VALUE),存储元素。
阻塞行为插入操作必须等待对应的移除操作,反之亦然。队列满时插入操作阻塞,队列空时移除操作阻塞。
适用场景直接传递任务,适合线程池任务调度。缓冲任务,适合生产者-消费者模式。

2. 内部实现

实现SynchronousQueueLinkedBlockingQueue
数据结构无存储结构,直接传递元素。基于链表实现,存储元素。
锁机制使用 CAS 或锁实现线程间的直接匹配。使用两把锁(putLocktakeLock)分离插入和移除操作。
公平性支持公平模式(FIFO)和非公平模式(默认)。默认非公平,但可以通过锁实现公平性。

3. 性能特点

性能SynchronousQueueLinkedBlockingQueue
吞吐量高吞吐量,适合直接传递任务的场景。吞吐量较低,因为需要维护队列结构。
延迟低延迟,任务直接传递给消费者。延迟较高,任务需要先入队再出队。
内存占用内存占用低,不存储元素。内存占用高,需要存储队列中的元素。

4. 使用场景

场景SynchronousQueueLinkedBlockingQueue
任务调度适合线程池任务调度(如 Executors.newCachedThreadPool)。适合需要缓冲任务的场景。
生产者-消费者适合生产者直接传递任务给消费者。适合生产者-消费者模式,任务需要缓冲。
流量控制无缓冲能力,严格限制生产者和消费者的同步。提供缓冲能力,适合流量控制。

5. 总结对比

对比项SynchronousQueueLinkedBlockingQueue
容量无容量,直接传递任务。有容量,可缓冲任务。
阻塞行为插入和移除操作必须成对出现。队列满时插入阻塞,队列空时移除阻塞。
性能高吞吐量,低延迟。吞吐量较低,延迟较高。
适用场景直接传递任务,适合线程池任务调度。缓冲任务,适合生产者-消费者模式。

选择使用哪种队列取决于具体的应用场景:

  • 如果需要直接传递任务且不需要缓冲,选择 SynchronousQueue
  • 如果需要缓冲任务并控制流量,选择 LinkedBlockingQueue

SynchronousQueue案例

如果你希望你的任务需要被快速处理,就可以使用这种队列。

JDK应用案例

Java线程池中的newCachedThreadPool(带缓存的线程池)底层就是使用SynchronousQueue实现的。

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

弊端:可能会出现oom问题。如果你提交了太多的任务,导致创建了大量的线程,这些线程都在竞争CPU时间片,等待CPU调度,处理任务速度也会变慢,所以在使用过程中也要综合考虑。


案例1:SynchronousQueue的简单用例

image-20250202153336290

package demo10.cpu;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;/*** @apiNote SynchronousQueue示例**/
public class SynchronousQueueDemo {public static void main(String[] args) throws InterruptedException {// 1. 创建SynchronousQueue队列BlockingQueue<Integer> synchronousQueue = new SynchronousQueue<>();// 2. 启动一个线程,往队列中放3个元素new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 入队列 1");synchronousQueue.put(1);Thread.sleep(1);System.out.println(Thread.currentThread().getName() + " 入队列 2");synchronousQueue.put(2);Thread.sleep(1);System.out.println(Thread.currentThread().getName() + " 入队列 3");synchronousQueue.put(3);} catch (InterruptedException e) {e.printStackTrace();}}).start();// 3. 等待1000毫秒Thread.sleep(1000L);// 4. 再启动一个线程,从队列中取出3个元素new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 出队列 " + synchronousQueue.take());Thread.sleep(1);System.out.println(Thread.currentThread().getName() + " 出队列 " + synchronousQueue.take());Thread.sleep(1);System.out.println(Thread.currentThread().getName() + " 出队列 " + synchronousQueue.take());} catch (InterruptedException e) {e.printStackTrace();}}).start();}}

image-20250202141622478

效果如下:第一个线程Thread-0往队列放入一个元素1后,就被阻塞了。直到第二个线程Thread-1从队列中取走元素1后,Thread-0才能继续放入第二个元素2。


案例2:SynchronousQueue公平锁、非公平锁案例

image-20250202153350415

为什么要多线程场景去测试?

  • 单线程你没法直接连续put多个,因为put和take操作是相对的,put了一个之后,只有take了另一个才能再进行put操作。
package demo11;import java.util.Arrays;
import java.util.concurrent.SynchronousQueue;/*** 公平 & 非公平 案例测试* 设置初始化的SynchronousQueue参数即可*/
public class SynchronousQueueFairAndNotFairDemo {public static void main(String[] args) throws InterruptedException {// 1. 创建SynchronousQueue队列,可设置是否公平SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>(false);// 放置三个元素 放置过程中各自等待500msfor (int i = 0; i < 10; i++) {// 2. 启动一个线程,往队列中放1个元素int finalI = i;new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "开始入队列" + finalI);synchronousQueue.put(finalI);
//                    System.out.println(Thread.currentThread().getName() + " 入队列" + finalI + "成功");} catch (InterruptedException e) {e.printStackTrace();}}).start();Thread.sleep(500);}// 3. 等待1000毫秒
//        Thread.sleep(1000L);// 取元素的时候各自间隔500msint[] arr = new int[10];for (int i = 0; i < 10; i++) {// 4. 启动一个线程,往队列中放1个元素int finalI = i;new Thread(() -> {try {arr[finalI] = synchronousQueue.take();System.out.println(Thread.currentThread().getName() + " 出队列 " + arr[finalI]);} catch (InterruptedException e) {e.printStackTrace();}}).start();Thread.sleep(500);}System.out.println(Arrays.toString(arr));// 7. 等待1000毫秒Thread.sleep(1000L);}
}

测试1:当设置非公平情况

SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>(false);

image-20250202153656442

测试2:当设置公平情况

SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>(true);

image-20250202153735555


案例3:搭配线程池使用

线程池多线程+SynchronousQueue(公平无效)

image-20250202173128343

配合线程池多线程场景,SynchronousQueue设置为公平无效,测试源码如下:

package demo11;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;/*** 线程池多线程场景(核心数、最大线程数非1场景)* 无公平可言原因:线程池底层走的是offer操作,并非是put操作(可见案例2中的demo场景,可实现公平是走的put操作实现)* 适合场景:cpu密集型,在多线程场景通过线程池是无法实现按照submit的提交顺序去处理逻辑的(原因如上)。*/
public class SynchronousQueueFairAndNotFairPoolDemo1 {public static void main(String[] args) throws InterruptedException {int cpuCores = Runtime.getRuntime().availableProcessors();ThreadPoolExecutor executor = new ThreadPoolExecutor(cpuCores + 1, cpuCores + 1,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(true),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {try {// 阻塞等待executor.getQueue().put(r);} catch (InterruptedException var4) {throw new RejectedExecutionException("Unexpected InterruptedException", var4);}}});// 倒计数int jobNum = 100;CountDownLatch countDownLatch = new CountDownLatch(jobNum);final AtomicInteger count = new AtomicInteger(0);// 记录任务开始时间long startTime = System.currentTimeMillis();//设计提交任务new Thread(()->{// 可改为queue队列int i = 0;while (i < jobNum) {int finalI = i;// submitjob的线程(非主线程)会进入到阻塞当中(保证按照顺序来执行)// 线程池底层使用的是队列的offer、pollexecutor.submit(()->{System.out.println("CPU执行任务:" + finalI + ", 计数 =>" + count.incrementAndGet());countDownLatch.countDown();});i++;}}).start();System.out.println("main主线程开始干活");countDownLatch.await();executor.shutdown();System.out.println("任务全部完成");// 记录任务结束时间long endTime = System.currentTimeMillis();// 计算任务执行时间long duration = endTime - startTime;System.out.println("任务执行总耗时: " + duration + " 毫秒");}
}

image-20250202173220376


线程池多线程+SynchronousQueue(公平有效)

image-20250202173234582

package demo11;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;/*** 线程池单线程场景(核心数、最大线程数1场景)* 可实现公平效果(无论是否设置公平参数)* 适合场景:任务按照submitjob去依次提交任务 & 去除线程池同样也可实现,可见下面处理方式*/
public class SynchronousQueueFairAndNotFairPoolDemo2 {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), // 填写false、true在核心、最大线程数为1 1情况下效果一致new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {try {executor.getQueue().put(r);} catch (InterruptedException var4) {throw new RejectedExecutionException("Unexpected InterruptedException", var4);}}});// 倒计数int jobNum = 100;CountDownLatch countDownLatch = new CountDownLatch(jobNum);final AtomicInteger count = new AtomicInteger(0);//设计提交任务new Thread(()->{// 可改为queue队列int i = 0;while (i < jobNum) {int finalI = i;// 处理方式一:submitjob的线程(非主线程)会进入到阻塞当中(保证按照顺序来执行)executor.submit(()->{System.out.println("CPU执行任务:" + finalI + ", 计数 =>" + count.incrementAndGet());countDownLatch.countDown();});// or 处理方式二:思考:是否如果原本就在新建线程中,是否无需使用线程池去submitjob提交?因为原本当前就是阻塞进行的
//                System.out.println("执行任务:" + finalI);i++;}}).start();// 7. 等待1000毫秒System.out.println("main主线程开始干活");countDownLatch.await();executor.shutdown();System.out.println("任务全部完成");}
}

效果:

image-20250202174350317

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

  • 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
  • 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
  • 学习与生活-专栏:可以了解博主的学习历程
  • 算法专栏:算法收录

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12713.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 缓存机制与架构解析

目录 一、MySQL缓存机制概述 二、MySQL整体架构 三、SQL查询执行全流程 四、MySQL 8.0为何移除查询缓存&#xff1f; 五、MySQL 8.0前的查询缓存配置 六、替代方案&#xff1a;应用层缓存与优化建议 总结 一、MySQL缓存机制概述 MySQL的缓存机制旨在提升数据访问效率&am…

【C++】STL——list的使用

目录 &#x1f495;1.带头双向链表List &#x1f495;2.list用法介绍 &#x1f495;3.list的初始化 &#x1f495;4.size函数与resize函数 &#x1f495;5.empty函数 &#x1f495;6.front函数与back函数 &#x1f495;7.push_front,push_back,pop_front,pop_back函数…

MySQL知识点总结(一)

1.SQL分类 数据定义&#xff08;DDL&#xff09;:创/改/删/名/清&#xff08;cadrt&#xff09; 数据库对象&#xff1a;表/视图/存储/函数/触发器/事件 数据操作&#xff08;DML&#xff09;&#xff1a;增/删/改/查&#xff08;idus&#xff09; 操作数据库对象 数据控制&…

快速幂,错位排序笔记

​ 记一下刚学明白的快速幂和错位怕排序的原理和代码 快速幂 原理&#xff1a; a^b (a^&#xff08;b/2&#xff09;) ^ 2&#xff08;b为偶数&#xff09; a^b a*&#xff08;a^&#xff08; (b-1)/2&#xff09;&#xff09;^2&#xff08;b为奇数&#xff09; 指数为偶数…

【缴纳过路费——并查集】

题目 分析 题目乍看一下像最短路径的求解。但是从复杂度上面分析应该不是这样。题目关键点在于“路程花费是最贵的那一段” 和 “最少花费在区间内”。 合起来就是两点所有路程中最便宜的最贵段&#xff0c;要在区间内&#xff1a;如果按权值从小到大遍历边&#xff0c;能合并连…

ComfyUI安装调用DeepSeek——DeepSeek多模态之图形模型安装问题解决(ComfyUI-Janus-Pro)

ComfyUI 的 Janus-Pro 节点&#xff0c;一个统一的多模态理解和生成框架。 试用&#xff1a; https://huggingface.co/spaces/deepseek-ai/Janus-1.3B https://huggingface.co/spaces/deepseek-ai/Janus-Pro-7B https://huggingface.co/spaces/deepseek-ai/JanusFlow-1.3B 安装…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.22 多项式运算:从求根到拟合的数值方法

2.22 多项式运算&#xff1a;从求根到拟合的数值方法 目录 #mermaid-svg-D0vp87eTMLHIY39y {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-D0vp87eTMLHIY39y .error-icon{fill:#552222;}#mermaid-svg-D0vp87eTMLHI…

【MySQL】MySQL经典面试题深度解析

文章目录 一、MySQL与C的深度结合1.1 为什么C项目需要MySQL&#xff1f;1.2 典型应用场景 二、基础概念面试题精讲2.1 存储引擎对比2.2 索引原理 三、C专项面试题解析3.1 连接池实现3.2 预处理语句3.3 批量操作优化 四、高级应用面试题剖析4.1 事务隔离级别4.2 锁机制详解4.3 查…

(脚本学习)BUU18 [CISCN2019 华北赛区 Day2 Web1]Hack World1

自用 题目 考虑是不是布尔盲注&#xff0c;如何测试&#xff1a;用"1^1^11 1^0^10&#xff0c;就像是真真真等于真&#xff0c;真假真等于假"这个测试 SQL布尔盲注脚本1 import requestsurl "http://8e4a9bf2-c055-4680-91fd-5b969ebc209e.node5.buuoj.cn…

互联网行业常用12个数据分析指标和八大模型

本文目录 前言 一、互联网线上业务数据分析的12个指标 1. 用户数据&#xff08;4个&#xff09; (1) 存量&#xff08;DAU/MAU&#xff09; (2) 新增用户 (3) 健康程度&#xff08;留存率&#xff09; (4) 渠道来源 2. 用户行为数据&#xff08;4个&#xff09; (1) 次数/频率…

Verilog语言学习总结

Verilog语言学习&#xff01; 目录 文章目录 前言 一、Verilog语言是什么&#xff1f; 1.1 Verilog简介 1.2 Verilog 和 C 的区别 1.3 Verilog 学习 二、Verilog基础知识 2.1 Verilog 的逻辑值 2.2 数字进制 2.3 Verilog标识符 2.4 Verilog 的数据类型 2.4.1 寄存器类型 2.4.2 …

知识蒸馏教程 Knowledge Distillation Tutorial

来自于&#xff1a;Knowledge Distillation Tutorial 将大模型蒸馏为小模型&#xff0c;可以节省计算资源&#xff0c;加快推理过程&#xff0c;更高效的运行。 使用CIFAR-10数据集 import torch import torch.nn as nn import torch.optim as optim import torchvision.tran…

使用SpringBoot发送邮件|解决了部署时连接超时的bug|网易163|2025

使用SpringBoot发送邮件 文章目录 使用SpringBoot发送邮件1. 获取网易邮箱服务的授权码2. 初始化项目maven部分web部分 3. 发送邮件填写配置EmailSendService [已解决]部署时连接超时附&#xff1a;Docker脚本Dockerfile创建镜像启动容器 1. 获取网易邮箱服务的授权码 温馨提示…

docker pull Error response from daemon问题

里面填写 里面解决方案就是挂代理。 以虚拟机为例&#xff0c;将宿主机配置端口设置&#xff0c;https/http端口设为7899 配置虚拟机的http代理&#xff1a; vim /etc/systemd/system/docker.service.d/http-proxy.conf里面填写&#xff0c;wq保存 [Service] Environment…

从Transformer到世界模型:AGI核心架构演进

文章目录 引言&#xff1a;架构革命推动AGI进化一、Transformer&#xff1a;重新定义序列建模1.1 注意力机制的革命性突破1.2 从NLP到跨模态演进1.3 规模扩展的黄金定律 二、通向世界模型的关键跃迁2.1 从语言模型到认知架构2.2 世界模型的核心特征2.3 混合架构的突破 三、构建…

Verilog基础(三):过程

过程(Procedures) - Always块 – 组合逻辑 (Always blocks – Combinational) 由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合. 然而,有时这不是描述电路最方便的方法. 两种always block是十分有用的: 组合逻辑: always @(…

STM32 串口发送与接收

接线图 代码配置 根据上一章发送的代码配置&#xff0c;在GPIO配置的基础上需要再配置PA10引脚做RX接收&#xff0c;引脚模式可以选择浮空输入或者上拉输入&#xff0c;在USART配置串口模式里加上RX模式。 配置中断 //配置中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE…

Docker技术相关学习三

一、Docker镜像仓库管理 1.docker仓库&#xff1a;用于存储和分发docker镜像的集中式存储库&#xff0c;开发者可以将自己创建的镜像推送到仓库中也可以从仓库中拉取所需要的镜像。 2.docker仓库&#xff1a; 公有仓库&#xff08;docker hub&#xff09;&#xff1a;任何人都可…

Java BIO详解

一、简介 1.1 BIO概述 BIO&#xff08;Blocking I/O&#xff09;&#xff0c;即同步阻塞IO&#xff08;传统IO&#xff09;。 BIO 全称是 Blocking IO&#xff0c;同步阻塞式IO&#xff0c;是JDK1.4之前的传统IO模型&#xff0c;就是传统的 java.io 包下面的代码实现。 服务…

【ArcGIS_Python】使用arcpy脚本将shape数据转换为三维白膜数据

说明&#xff1a; 该专栏之前的文章中python脚本使用的是ArcMap10.6自带的arcpy&#xff08;好几年前的文章&#xff09;&#xff0c;从本篇开始使用的是ArcGIS Pro 3.3.2版本自带的arcpy&#xff0c;需要注意不同版本对应的arcpy函数是存在差异的 数据准备&#xff1a;准备一…