CountDownLatch、CyclicBarrier 和 Semaphore

文章目录

  • 一、CountDownLatch
    • 1、实现原理
    • 2、使用场景
    • 3、代码
  • 二、CyclicBarrier
    • 1、实现原理
    • 2、使用场景
    • 3、代码
    • 4、CountDownLatch与CyclicBarrier区别
  • 三、Semaphore
    • 1、实现原理
    • 2、使用场景
    • 3、代码
  • 四、总结

一、CountDownLatch

CountDownLatch计数器

1、实现原理

主要基于计数器和阻塞队列。

CountDownLatch 内部维护一个计数器,这个计数器的初始值通常设置为需要等待的线程数量。当一个线程调用 CountDownLatch 的 await() 方法时,如果计数器的值大于 0,则该线程会被放入一个阻塞队列中等待,并处于挂起状态。每当一个线程完成了自己的任务后,它会调用 CountDownLatch 的 countDown() 方法,使计数器递减。当计数器的值递减到 0 时,CountDownLatch 会唤醒阻塞队列中所有等待的线程,使它们能够继续执行后续的任务。

2、使用场景

用于等待多个线程完成后进行指定操作。

常见场景:

  • 服务启动时要等待多个资源初始化
  • 并行任务处理,有多个并行处理的任务,并且需要在任务都处理完毕后,再做其他处理。比如:并行计算成绩,最终汇总分数;分开去多个服务查询前置数据,然后进行校验
  • 模拟高并发测试,在测试一个多线程并发访问的共享资源时,可以使用 CountDownLatch 来确保所有线程都准备好访问共享资源后再进行实际测试。
  • 异步编程中的等待机制,等待某个异步操作完成后才继续执行后续代码

3、代码

伪代码如下:

// 初始化 CountDownLatch,计数器设为 N  
CountDownLatch latch = new CountDownLatch(N);  // 在 N 个线程中  
for (int i = 0; i < N; i++) {  new Thread(() -> {  // 执行一些任务  // ...  // 任务完成后,计数器减一  latch.countDown();  }).start();  
}  // 在主线程中等待所有线程完成任务  
latch.await(); // 阻塞直到计数器为0  // 所有任务都已完成,继续执行后续代码  
// ...

案例:主要模拟3个任务并行,然后主线程阻塞等待

static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {// 多个任务并发执行,都执行完毕后,再执行主线程,也就是await的线程System.out.println("主业务开始执行");sleep(1000);executor.execute(CountDownLatchTest::a);executor.execute(CountDownLatchTest::b);executor.execute(CountDownLatchTest::c);System.out.println("三个任务并行执行,主业务线程等待");// 死等任务结束// countDownLatch.await();// 如果在规定时间内,任务没有结束,返回falseif (countDownLatch.await(2, TimeUnit.SECONDS)) {System.out.println("三个任务处理完毕,主业务线程继续执行");} else {System.out.println("三个任务没有全部处理完毕,执行其他的操作");}
}private static void a() {System.out.println("A任务开始");sleep(3000);System.out.println("A任务结束");countDownLatch.countDown();
}
private static void b() {System.out.println("B任务开始");sleep(1500);System.out.println("B任务结束");countDownLatch.countDown();
}
private static void c() {System.out.println("C任务开始");sleep(2000);System.out.println("C任务结束");countDownLatch.countDown();
}
private static void sleep(long timeout) {try {Thread.sleep(timeout);} catch (InterruptedException e) {e.printStackTrace();}
}

其中await()是死等任务结束,不限制时间;await(long timeout, TimeUnit unit)是在规定时间内任务没有结束,就返回false

二、CyclicBarrier

CyclicBarrier栅栏

1、实现原理

主要基于计数器、等待队列、循环栅栏。

CyclicBarrier 内部维护一个计数器,用于记录当前到达屏障点的线程数量,就是我们创建时指定的线程数。当一个线程到达屏障点时,如果计数器的值大于 0,则该线程会被放入一个等待队列中等待,并处于挂起状态,如果计数器的值变为 0,则说明所有线程都已到达屏障点,直接唤醒等待队列中的所有线程,并继续执行后续任务。在 CyclicBarrier 的构造函数中,可以指定一个可选的栅栏动作。当所有线程都到达屏障点时,这个栅栏动作会被执行一次,然后重置回初始状态并再次使用。

  • Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当一个线程到达屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后的任务。基于ReentrantLock锁的await方法阻塞在屏障点。
  • Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。

2、使用场景

用于让一组线程在某个屏障点相互等待,直到所有线程都到达该屏障点,然后它们才能继续执行。

常见场景:

  • 将任务分解成多个阶段,每个阶段由一组线程执行,并且需要在所有阶段完成后才能继续下一个阶段,比如游戏中所有人到达终点,才开启下一关

3、代码

伪代码如下:

// 初始化 CyclicBarrier,参与线程数为 N,可选的屏障动作(barrierAction)  
CyclicBarrier barrier = new CyclicBarrier(N, () -> {  // 所有线程到达屏障点时执行的代码  // ...  
});  // 在 N 个线程中  
for (int i = 0; i < N; i++) {  new Thread(() -> {  // 执行一些任务  // ...  // 到达屏障点,等待其他线程  barrier.await(); // 阻塞直到所有线程到达  // 所有线程都已到达屏障点,继续执行后续代码  // ...  }).start();  
}

案例:大家集合完毕后,再一起出发

CyclicBarrier barrier = new CyclicBarrier(3,() -> {System.out.println("各位大佬集合完毕,发护照准备出发!");
});
new Thread(() -> {System.out.println("Tom到位!!!");try {barrier.await();} catch (Exception e) {System.out.println("悲剧,人没到齐!");return;}System.out.println("Tom出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {System.out.println("Jack到位!!!");try {barrier.await();} catch (Exception e) {System.out.println("悲剧,人没到齐!");return;}System.out.println("Jack出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {System.out.println("Rose到位!!!");try {barrier.await();} catch (Exception e) {System.out.println("悲剧,人没到齐!");return;}System.out.println("Rose出发!!!");
}).start();

4、CountDownLatch与CyclicBarrier区别

  • 底层实现不同:CountDownLatch基于AQS。CyclicBarrier基于ReentrantLock。
  • 应用场景不同:CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器,可以实现相比CountDownLatch更复杂的业务,如果执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
  • 等待对象不同:CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒

三、Semaphore

Semaphore(信号量),保证x个资源可以被多个线程同时访问

1、实现原理

Semaphore底层也是基于AQS的state属性做一个计数器的维护。state的值就代表当前共享资源的个数。如果一个线程需要获取的x个资源,
直接查看state的标识的资源个数是否足够,如果足够的,直接对state-x拿到当前资源。如果资源不够,当前线程就需要挂起等待。
知道持有资源的线程释放资源后,会归还给Semaphore中的state属性,挂起的线程就可以被唤醒。

2、使用场景

用于控制对共享资源的并发访问数量。它维护了一个可用的许可证数量,并允许线程通过获取(acquire)和释放(release)许可证来访问资源。当没有可用许可证时,线程会等待。

常见场景:

  • 数据库连接池管理,限制同时访问数据库连接的线程数量
  • 线程池管理,限制同时执行的线程数量
  • 实现互斥锁,Semaphore的初始值设置为1,确保同一时间只能有一个线程可以访问
  • 流量控制,平衡系统的负载和资源利用

3、代码

伪代码如下:

// 初始化 Semaphore,允许同时访问的线程数为 M  
Semaphore semaphore = new Semaphore(M);  // 在多个线程中  
for (int i = 0; i < 任意数量; i++) {  new Thread(() -> {  // 请求一个许可  semaphore.acquire(); // 阻塞直到有一个许可可用  try {  // 进入临界区,执行受保护的代码  // ...  // 临界区结束  } finally {  // 释放一个许可  semaphore.release();  }  // 继续执行其他代码  // ...  }).start();  
}

案例:环球影城,每天接受的人流量是固定的,每有一个人购票后,就对信号量进行–操作,如果信号量已经达到了0,或者是资源不足,此时就不能买票。

Semaphore semaphore = new Semaphore(10);
new Thread(() -> {System.out.println("一家三口来了");try {semaphore.acquire(3);System.out.println("一家三口进去了~~~");Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println("一家三口走了~~~");semaphore.release(3);}
}).start();
for (int i = 0; i < 7; i++) {int j = i;new Thread(() -> {System.out.println(j + "大哥来了");try {semaphore.acquire();System.out.println(j + "大哥进去了~~~");Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(j + "大哥走了~~~");semaphore.release();}}).start();
}
Thread.sleep(2000);
System.out.println("main大哥来了");
if (semaphore.tryAcquire()) {System.out.println("main大哥进去了~~~");
} else {System.out.println("资源不够,main大哥停止进去");
}
Thread.sleep(3000);
System.out.println("main大哥又来了");
if (semaphore.tryAcquire()) {System.out.println("main大哥进去了~~~");semaphore.release();
} else {System.out.println("资源不够,main大哥停止进去");
}

四、总结

总的来说,CountDownLatch、CyclicBarrier 和 Semaphore 是 JVM 级别的同步工具,它们的状态是存储在JVM 的内存中的,主要用于单个 JVM 进程内的线程同步和协作,主要用于解决多线程编程中的一些问题,例如等待多个线程完成某些任务、让一组线程在某个点同步继续执行,或者限制对共享资源的并发访问数量。而在分布式场景下并不直接适用,可以考虑其它解决方案来实现类似的功能,比如:分布式锁、数据库事务、外部存储系统等

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

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

相关文章

Window7镜像注入USB驱动,解决系统安装后无法识别USB

Window7镜像注入usb驱动 Window7镜像注入usb驱动方法一方法二 Window7镜像注入usb驱动 一般4代酷睿之后的主机需要安装usb驱动才能驱动usb&#xff0c;导致很多Windows原版镜像安装后无法识别usb键盘 方法一 1.直接采购PS2 接口键盘、PS2 接口鼠标 方法二 使用联想镜像注入…

李飞飞团队 AI4S 最新洞察:16 项创新技术汇总,覆盖生物/材料/医疗/问诊……

不久前&#xff0c;斯坦福大学 Human-Center Artificial Intelligence (HAI) 研究中心重磅发布了《2024年人工智能指数报告》。 作为斯坦福 HAI 的第七部力作&#xff0c;这份报告长达 502 页&#xff0c;全面追踪了 2023 年全球人工智能的发展趋势。相比往年&#xff0c;扩大了…

AOF持久化是怎么实现的?

AOF持久化是怎么实现的&#xff1f; AOF 日志三种写回策略AOF 重写机制AOF 后台重写总结参考资料 AOF 日志 试想一下&#xff0c;如果 Redis 每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到一个文件里&#xff0c;然后重启 Redis 的时候&#xff0c;先去读取这…

在k8s中部署hadoop后的使用,包括服务端及客户端(客户端的安装及与k8s服务的对接)

&#xff08;作者&#xff1a;陈玓玏&#xff09; 在https://blog.csdn.net/weixin_39750084/article/details/136744772?spm1001.2014.3001.5502和https://blog.csdn.net/weixin_39750084/article/details/136750613?spm1001.2014.3001.5502这两篇文章中&#xff0c;说明…

Verilog复习(一)| 模块的定义

模块&#xff08;module&#xff09;是Verilog的基本描述单位&#xff0c;用于描述某个设计的功能或结构&#xff0c;及其与其他模块通信&#xff08;连接&#xff09;的外部端口。 Verilog程序由关键词module和endmodule进行定义。 定义模块的步骤&#xff1a; 定义模块的端…

保研面试408复习 4——操作系统、计网

文章目录 1、操作系统一、文件系统中文件是如何组织的&#xff1f;二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议&#xff08;数据链路层协议&#xff09;二、以太网MAC帧MTU 标记文字记忆&#xff0c;加粗文字注意&#xff0c;普通文字理解。 1、…

全平台 GUI库, 物联网,嵌入式,单片机,桌面应用都行

跨平台最小头文件GUI库 GuiLite是一个轻量级、高效的GUI库&#xff0c;拥有仅4千行的C代码&#xff0c;且零依赖&#xff0c;采用单一头文件库&#xff08;GuiLite.h&#xff09;。这个库不仅提供高效渲染&#xff0c;即使在单片机上也能流畅运行&#xff0c;展现了卓越的性能表…

【比邻智选】MR880A模组

&#x1f680;高性价比&#xff0c;5G/4G双模&#xff0c;稳定可靠 &#x1f310;功能丰富&#xff0c;5G特性一应俱全 &#x1f9e9;多封装兼容&#xff0c;适配性强&#xff0c;灵活升级智能设备

这 7 道 Redis 基础问题,很常见!!

后端项目如果用到分布式缓存的话&#xff0c;一般用的都是 Redis。不过&#xff0c;Redis 不仅仅能做缓存&#xff0c;还能用作分布式锁、延时队列、限流等等。 什么是 Redis&#xff1f; Redis[1] &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的…

Unity数据持久化之Json

目录 Json概述Json文件格式Json配置规则Excel转Json C#读取存储Json文件JsonUtlityJsonUtlity序列化JsonUtility反序列化 LitJsonLitJson序列化LitJson反序列化JsonUtility和LitJson对比 Json概述 Json是什么? 全称:JavaScript对象简谱(JavaScript Object Notation) Json是国…

ESP8266-01s刷入固件报SP8266 Chip efuse check error esp_check_mac_and_efuse

一、遇到的问题 使用ESP8266 固件烧录工具flash_download_tools_v3.6.8 烧录固件报错&#xff1a; 二、解决方法 使用espressif推出发基于python的底层烧写工具&#xff1a;esptool 安装方法&#xff1a;详见https://docs.espressif.com/projects/esptool/en/latest/esp32/ …

子查询之一(单行子查询, 多行子查询)

1. 子查询 子查询是指一个查询语句嵌套在另一个查询语句内部的查询.这个特性在MySQL4.1开始引入. SQL中子查询的使用大大增强了SELECT查询的能力.因为很多时候查询需要从结果集中获取数据&#xff0c;或者需要从同一个表中先计算得到一个数据结果&#xff0c;然后与这个数据结…

【go项目01_学习记录08】

学习记录 1 模板文件1.1 articlesStoreHandler() 使用模板文件1.2 统一模板 1 模板文件 重构 articlesCreateHandler() 和 articlesStoreHandler() 函数&#xff0c;将 HTML 抽离并放置于独立的模板文件中。 1.1 articlesStoreHandler() 使用模板文件 . . . func articlesSt…

最新:Lodash 严重安全漏洞背后你不得不知道的 JavaScript 知识

可能有信息敏感的同学已经了解到&#xff1a;Lodash 库爆出严重安全漏洞&#xff0c;波及 400万 项目。这个漏洞使得 lodash “连夜”发版以解决潜在问题&#xff0c;并强烈建议开发者升级版本。 我们在忙着“看热闹”或者“”升级版本”的同时&#xff0c;静下心来想&#xf…

FebHost:什么是域名DNS服务器?

域名服务器是一种将域名转换为IP地址的计算机。在域名系统&#xff08;DNS&#xff09;中&#xff0c;它起着至关重要的作用。用户只需在浏览器的地址栏输入域名&#xff0c;而无需手动输入网站服务器的IP地址&#xff0c;就可以访问网站。 每个已注册的域名都必须在其DNS记录…

震惊,现在面试都加科技与狠货了

震惊&#xff0c;现在面试都加科技与狠货了 生成式AI盛行的现在&#xff0c;程序员找工作变容易了吗我和老痒喝着大酒&#xff0c;吃着他的高升宴&#xff0c;听他说他面试的各种细节&#xff0c;老狗我只恨自己动作慢了一步&#xff0c;不然现在在那侃侃而谈的就是我了。 面试…

守护数字疆域:2024年网络安全报告深度解读

在这个数据如潮涌动的数字时代&#xff0c;每一比特信息都可能是攻防双方角力的战场。《Check Point 2024年网络安全报告》不但为我们揭示了过去一年网络安全世界的风云变幻&#xff0c;更以前瞻性的视角勾勒出未来的挑战与机遇。此刻&#xff0c;让我们携手深潜这份权威指南的…

分位数回归的基本原理和特点

基本模型及解释 分位数回归经典模型是由Koenker和Bassett (1978b)引入的&#xff0c;它从位置模型中的普通分位数(也称为“百分位数”)的概念扩展到更一般的一类线性模型&#xff0c;其中条件分位数具有线性形式。为了简单地回忆一下普通分位数&#xff0c;考虑一个实数随机变…

QGraphicsView实现简易地图11『指定层级-定位坐标』

前文链接&#xff1a;QGraphicsView实现简易地图10『自适应窗口大小』 提供一个地图初始化函数&#xff0c;指定地图显示的中心点和地图缩放层级 能够让地图显示某一层级的瓦片&#xff0c;并将中心点坐标显示在视图中心。 1、动态演示效果 7级地图-大连-老虎滩 定位到 8级地图…