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

文章标题:如何使用 CountDownLatch 和 CyclicBarrier?
  • 文章分类: 后端
  • 6918 阅读

在并发编程中,CountDownLatchCyclicBarrier 是 Java 并发包(java.util.concurrent)中两个非常有用的同步工具,它们各自适用于不同的场景,但共同点是都能帮助协调多个线程之间的操作顺序。下面,我将详细解释这两个工具的使用场景、工作原理,并通过实例代码展示如何在实际编程中应用它们。

CountDownLatch

CountDownLatch 是一种同步工具,它允许一个或多个线程等待其他线程完成一组操作后再继续执行。CountDownLatch 的工作原理是,在初始化时设置一个计数器(count),任何线程都可以调用 countDown() 方法来减少这个计数器的值。当计数器的值减至零时,那些因为调用 await() 方法而阻塞的线程将被唤醒,继续执行后续操作。

使用场景

  • 并行计算:当你需要将一个大的计算任务分割成多个小任务,并希望在所有小任务都完成后才进行结果汇总时。
  • 资源初始化:在多个线程需要使用某个资源之前,必须确保该资源已经被完全初始化。

示例代码

假设我们有一个场景,需要同时从多个数据源加载数据,并在所有数据加载完成后才进行汇总处理。

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 的计数器一旦到达零,就无法重置。

使用场景

  • 并行计算后同步处理:多个线程并行执行计算任务,所有线程都完成后需要进行一些共同的后续处理。
  • 游戏开发:在游戏的多人模式中,需要等待所有玩家都准备好后才能开始游戏。

示例代码

考虑一个场景,一组科学家(线程)正在各自的研究领域进行研究,但他们都需要等待所有人的研究都完成后才能一起开始撰写报告。

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();
        }
    }
}

总结

CountDownLatchCyclicBarrier 都是强大的并发工具,它们各自适用于不同的场景。CountDownLatch 适用于等待一组操作的完成,而 CyclicBarrier 则适用于需要所有参与者都到达某个公共点后才能继续执行的场景。在实际开发中,根据具体需求选择合适的工具,可以大大提高程序的并发性能和可读性。

通过上面的示例代码,我们可以看到,这两个工具的使用都相对直观,只需要正确设置计数器或屏障点,并在合适的位置调用 countDown()await() 等方法即可。此外,它们还提供了异常处理机制,确保在多线程环境下程序的健壮性。

在深入理解这两个工具的基础上,你还可以探索 Java 并发包中的其他同步工具,如 Semaphore(信号量)、Exchanger(交换器)等,它们各自具有独特的功能和适用场景,能够为你的并发编程提供更多的选择。

最后,值得一提的是,码小课作为一个专注于编程学习和分享的平台,提供了丰富的课程资源和实战项目,帮助开发者不断提升自己的编程技能。如果你对并发编程或 Java 并发包中的其他内容感兴趣,不妨前往码小课网站,探索更多精彩内容。

推荐文章