当前位置: 首页 > article >正文

CountDownLatch 和 CyclicBarrier 使用场景详解

CountDownLatch 和 CyclicBarrier 使用场景详解

在并发编程中,Java 提供了多种工具来帮助管理线程之间的协调。CountDownLatchCyclicBarrier 是两种常用的同步工具类,它们虽然功能不同,但都用于线程之间的协调与同步。


1. CountDownLatch 使用场景

CountDownLatch 是一个线程同步工具,它允许一个或多个线程等待其他线程完成一组操作。这种机制非常适用于以下场景:

  • 并行任务的等待:当你有多个子任务需要并行执行,并且在所有子任务完成后再继续执行主任务时,CountDownLatch 是理想的选择。
  • 一次性事件触发:某些场景下,你可能需要在特定数量的操作完成后触发某个事件,比如多线程的初始化工作。
代码示例:并行任务的等待
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {private static final int TASK_COUNT = 3;public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(TASK_COUNT);for (int i = 0; i < TASK_COUNT; i++) {new Thread(new Task(latch)).start();}// 等待所有任务完成latch.await();System.out.println("所有任务已完成,继续主线程工作");}static class Task implements Runnable {private final CountDownLatch latch;Task(CountDownLatch latch) {this.latch = latch;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 正在执行任务...");// 模拟任务耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(Thread.currentThread().getName() + " 任务完成");latch.countDown(); // 每个线程完成后将计数器减1}}
}
源码分析

CountDownLatch 的核心在于它维护了一个计数器,该计数器在初始化时设置为指定的数量。每当一个线程完成任务后,会调用 countDown() 方法将计数器减1。当计数器归零时,所有在 await() 方法上等待的线程将继续执行。

public void countDown() {sync.releaseShared(1);
}public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
  • countDown() 方法:通过 sync.releaseShared(1) 来递减计数器。
  • await() 方法:调用 sync.acquireSharedInterruptibly(1) 来阻塞当前线程,直到计数器为零。
场景还原
场景一

假设你有一个任务需要从多个数据源中获取数据,并在所有数据都获取到之后进行处理。在这种场景下,CountDownLatch 可以确保主线程在所有数据获取完毕之后再开始处理。

场景二:大数据处理中的分布式计算同步

在大数据处理场景中,通常需要将一个大任务分解为多个小任务,并分布在多个节点上进行计算。每个计算节点在完成自己的部分任务后,都需要等待其他节点完成,然后进行数据的聚合。CyclicBarrier 可以用于这种场景,确保所有节点都完成任务后再继续下一步的聚合处理。

逻辑图:

2. CyclicBarrier 使用场景

CyclicBarrier 也是一个线程同步工具,它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点。与 CountDownLatch 不同,CyclicBarrier 可以在屏障点被释放后再次使用,因此适用于多次使用的场景。

  • 分段任务处理:当任务被分成多个阶段,每个阶段都需要所有线程完成后才能继续下一个阶段时,CyclicBarrier 是最佳选择。
  • 多线程模拟:在模拟多线程同时开始的场景中,比如多线程并发测试。
代码示例:分段任务处理
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierExample {private static final int THREAD_COUNT = 3;private static CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {System.out.println("所有任务准备完毕,开始执行下一阶段");});public static void main(String[] args) {for (int i = 0; i < THREAD_COUNT; i++) {new Thread(new Task()).start();}}static class Task implements Runnable {@Overridepublic void run() {try {System.out.println(Thread.currentThread().getName() + " 正在执行第一阶段...");Thread.sleep(1000); // 模拟第一阶段任务barrier.await(); // 等待其他线程完成第一阶段System.out.println(Thread.currentThread().getName() + " 正在执行第二阶段...");Thread.sleep(1000); // 模拟第二阶段任务barrier.await(); // 等待其他线程完成第二阶段} catch (InterruptedException | BrokenBarrierException e) {Thread.currentThread().interrupt();}}}
}
源码分析

CyclicBarrier 的内部维护了一个计数器,当所有参与线程都调用 await() 方法并达到屏障点时,计数器归零,并触发 Runnable 任务。

public int await() throws InterruptedException, BrokenBarrierException {return dowait(false, 0L);
}private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {final ReentrantLock lock = this.lock;lock.lock();try {// 计数器减1,直到为0final Generation g = generation;final int index = --count;if (index == 0) {  // 所有线程都已到达nextGeneration();  // 进入下一代return 0;}...} finally {lock.unlock();}
}
  • await() 方法:通过 dowait 方法实现线程的同步等待,并在计数器归零后重置屏障,进入下一代。
  • nextGeneration() 方法:重置 countgeneration,使得屏障可以重新使用。
场景还原
场景一

假设你在开发一个复杂的算法,其分为多个步骤,每个步骤都需要多个线程协同工作并在同一时间点完成。CyclicBarrier 可以确保所有线程都在完成当前步骤后,统一进入下一步骤。

场景二:模拟多人游戏中的回合制同步

在多人在线游戏中,常常需要在回合制游戏中同步所有玩家的行动结果,然后进入下一个回合。例如,多个玩家在同一回合中选择动作,所有玩家的动作都完成后,统一执行这些动作,再进入下一回合。

逻辑图:

   Player1  |
(CyclicBarrier.await)Player2  |
(CyclicBarrier.await)Player3  |
(CyclicBarrier.await)|
+---------------------------+
| 执行本回合的所有玩家动作 |  
+---------------------------+|
+---------------------------+
| 准备下一个回合            |  
+---------------------------+|
(CyclicBarrier.await)

业务逻辑:

  1. 每个玩家选择动作并准备好后调用 CyclicBarrier.await()
  2. 当所有玩家都准备好后,游戏服务器执行所有玩家的动作。
  3. 进入下一回合,重复上述步骤。
场景三:多线程模拟交易系统的负载测试

在金融交易系统中,经常需要进行负载测试,以模拟高并发情况下系统的表现。假设要模拟多用户同时提交交易请求,可以使用 CyclicBarrier 来同步多个模拟用户,使它们在同一时刻开始交易操作,从而测试系统在高负载下的性能表现。

逻辑图:

   User1  |
(CyclicBarrier.await)User2  |
(CyclicBarrier.await)User3  |
(CyclicBarrier.await)|
+---------------------------+
| 同时提交交易请求          |  
+---------------------------+|
+---------------------------+
| 交易系统处理              |  
+---------------------------+

业务逻辑:

  1. 多个线程分别模拟不同用户,准备好交易请求后调用 CyclicBarrier.await()
  2. 所有用户都准备好后,系统在同一时间接收到所有交易请求。
  3. 交易系统处理请求,并记录性能数据,用于分析系统在高负载下的表现。
3. 注意点
  • CountDownLatch 是一次性的,计数器一旦归零便不能重置;而 CyclicBarrier 是可以重复使用的。
  • 在使用 CountDownLatch 时,确保所有 countDown() 调用都能正确执行,否则会导致 await() 永远阻塞。
  • 在使用 CyclicBarrier 时,注意处理 BrokenBarrierException 异常,该异常通常在屏障被破坏时抛出,如有线程中途退出。
http://www.lryc.cn/news/2417754.html

相关文章:

  • 2024年安卓最全理解Android虚拟机体系结构,2024年最新Android高级工程师进阶学习
  • U盘启动盘怎么制作?
  • Regression算法之通俗讲解
  • UTF-8基础
  • 算法刷题笔记——动态规划篇
  • 损失函数MSE和MAE的区别以及如何选择
  • c语言md5函数头文件,【C】md5函数实现代码
  • 【Java】lambda表达式的3种写法
  • MyCat 管理及监控
  • 高级 Perl:文件处理与模块使用
  • 一文彻底了解ES6中的var、let、const基本用法以及暂时性死区和变量提升的区别
  • 深入浅出单例模式(全网最详细且通俗易懂讲解)
  • GPIO是啥
  • 都这麽大了还不快了解IDS?
  • HashMap笔记(自用+更新中)
  • String.format()的使用
  • 蓝牙beacon 功能简介
  • Kylin的介绍、使用和原理架构(Kylin3.0和Kylin4.0,Cube,去重原理,性能优化,MDX For Kylin,BI工具集成)
  • C++ MFC程序框架结构解析(详细)
  • vue系列 —— vue-route详细使用方法
  • Sqlmap常用命令总结
  • sudo权限管理
  • 是不是都把SELinux给忘了?
  • git命令归纳整理及如何使用
  • JDBC连接数据库小白级教程
  • 设计模式学习(三):Adapter适配器模式
  • JavaFX17 现代 Java 客户端权威指南(七)
  • Unity-3D游戏开发套件指南(入门篇)-免费资源
  • 如何快速而准确地进行 IP 和端口信息扫描:渗透测试必备技能
  • PID超详细教程——PID原理+串级PID+C代码+在线仿真调参