当前位置: 技术文章>> 如何使用 CountDownLatch 和 CyclicBarrier?

文章标题:如何使用 CountDownLatch 和 CyclicBarrier?
  • 文章分类: 后端
  • 6899 阅读
在并发编程中,`CountDownLatch` 和 `CyclicBarrier` 是 Java 并发包(`java.util.concurrent`)中两个非常有用的同步工具,它们各自适用于不同的场景,但共同点是都能帮助协调多个线程之间的操作顺序。下面,我将详细解释这两个工具的使用场景、工作原理,并通过实例代码展示如何在实际编程中应用它们。 ### CountDownLatch `CountDownLatch` 是一种同步工具,它允许一个或多个线程等待其他线程完成一组操作后再继续执行。`CountDownLatch` 的工作原理是,在初始化时设置一个计数器(count),任何线程都可以调用 `countDown()` 方法来减少这个计数器的值。当计数器的值减至零时,那些因为调用 `await()` 方法而阻塞的线程将被唤醒,继续执行后续操作。 #### 使用场景 - **并行计算**:当你需要将一个大的计算任务分割成多个小任务,并希望在所有小任务都完成后才进行结果汇总时。 - **资源初始化**:在多个线程需要使用某个资源之前,必须确保该资源已经被完全初始化。 #### 示例代码 假设我们有一个场景,需要同时从多个数据源加载数据,并在所有数据加载完成后才进行汇总处理。 ```java import java.util.concurrent.CountDownLatch; public class DataLoader { private final CountDownLatch latch; public DataLoader(int count) { this.latch = new CountDownLatch(count); } public void loadData(int dataSourceId) { // 模拟数据加载过程 try { System.out.println("数据源 " + dataSourceId + " 开始加载数据..."); Thread.sleep(1000); // 假设每个数据源加载数据需要1秒 System.out.println("数据源 " + dataSourceId + " 数据加载完成."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 加载完成后,减少计数器 } } public void waitForAllDataLoaded() throws InterruptedException { latch.await(); // 等待所有数据源加载完成 System.out.println("所有数据加载完成,开始汇总处理."); } public static void main(String[] args) { DataLoader loader = new DataLoader(3); // 假设有三个数据源 // 启动三个线程加载数据 for (int i = 1; i <= 3; i++) { new Thread(() -> loader.loadData(i)).start(); } try { loader.waitForAllDataLoaded(); // 等待所有数据加载完成 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } ``` ### CyclicBarrier `CyclicBarrier` 允许一组线程互相等待,直到达到某个公共屏障点(common barrier point)。与 `CountDownLatch` 不同的是,`CyclicBarrier` 在屏障点处可以执行自定义的屏障操作(barrier action),而且所有线程都必须到达屏障点才能继续执行。此外,`CyclicBarrier` 是可以重复使用的,而 `CountDownLatch` 的计数器一旦到达零,就无法重置。 #### 使用场景 - **并行计算后同步处理**:多个线程并行执行计算任务,所有线程都完成后需要进行一些共同的后续处理。 - **游戏开发**:在游戏的多人模式中,需要等待所有玩家都准备好后才能开始游戏。 #### 示例代码 考虑一个场景,一组科学家(线程)正在各自的研究领域进行研究,但他们都需要等待所有人的研究都完成后才能一起开始撰写报告。 ```java import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Scientist { private final CyclicBarrier barrier; public Scientist(CyclicBarrier barrier) { this.barrier = barrier; } public void doResearch() { try { // 模拟研究过程 System.out.println(Thread.currentThread().getName() + " 开始研究..."); Thread.sleep((long) (Math.random() * 3000)); // 假设研究时间随机 System.out.println(Thread.currentThread().getName() + " 研究完成."); // 所有科学家都研究完成后,才能继续 barrier.await(); // 所有科学家都到达屏障点后执行的操作 System.out.println(Thread.currentThread().getName() + " 开始撰写报告."); } catch (InterruptedException | BrokenBarrierException e) { Thread.currentThread().interrupt(); } } public static void main(String[] args) { int scientists = 4; // 假设有4位科学家 CyclicBarrier barrier = new CyclicBarrier(scientists, () -> System.out.println("所有科学家都已准备好,开始撰写报告.")); for (int i = 1; i <= scientists; i++) { new Thread(() -> new Scientist(barrier).doResearch(), "科学家" + i).start(); } } } ``` ### 总结 `CountDownLatch` 和 `CyclicBarrier` 都是强大的并发工具,它们各自适用于不同的场景。`CountDownLatch` 适用于等待一组操作的完成,而 `CyclicBarrier` 则适用于需要所有参与者都到达某个公共点后才能继续执行的场景。在实际开发中,根据具体需求选择合适的工具,可以大大提高程序的并发性能和可读性。 通过上面的示例代码,我们可以看到,这两个工具的使用都相对直观,只需要正确设置计数器或屏障点,并在合适的位置调用 `countDown()`、`await()` 等方法即可。此外,它们还提供了异常处理机制,确保在多线程环境下程序的健壮性。 在深入理解这两个工具的基础上,你还可以探索 Java 并发包中的其他同步工具,如 `Semaphore`(信号量)、`Exchanger`(交换器)等,它们各自具有独特的功能和适用场景,能够为你的并发编程提供更多的选择。 最后,值得一提的是,码小课作为一个专注于编程学习和分享的平台,提供了丰富的课程资源和实战项目,帮助开发者不断提升自己的编程技能。如果你对并发编程或 Java 并发包中的其他内容感兴趣,不妨前往码小课网站,探索更多精彩内容。
推荐文章