返回

揭秘CountDownLatch和CyclicBarrier的幕后真相:并发编程的黄金搭档

后端

CountDownLatch 和 CyclicBarrier:并发编程的黄金搭档

在并发编程的广阔世界中,经常会遇到需要协调多个线程同时完成任务的情况。为了避免线程执行不当而导致的数据不一致或死锁问题,Java 为我们提供了两个强大的同步工具:CountDownLatch 和 CyclicBarrier。

CountDownLatch:让所有线程齐步走

想象一下,你有一群热情的跑步者准备开始比赛。为了确保所有人都同时起跑,你需要一个信号来通知他们可以出发了。CountDownLatch 就充当了这个信号。

CountDownLatch 是一个计数器,可以让我们指定一个初始值。每个需要等待的线程都会调用 CountDownLatch 的 await() 方法,就像跑步者在起跑线前等待裁判的枪声。当所有线程都调用了 await(),CountDownLatch 的计数器就会减为 0。此时,CountDownLatch 会发出信号,唤醒所有正在等待的线程,就像裁判挥动绿旗,让跑步者们冲出起跑线。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        // 创建一个 CountDownLatch,初始值为 3
        CountDownLatch latch = new CountDownLatch(3);

        // 创建 3 个线程,每个线程都将递减 CountDownLatch 的计数器
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 完成任务");
                latch.countDown(); // 递减 CountDownLatch 的计数器
            }).start();
        }

        // 主线程等待所有线程完成任务
        latch.await(); // 阻塞当前线程,直到 CountDownLatch 的计数器减为 0
        System.out.println("所有线程完成任务");
    }
}

CyclicBarrier:循环等待,步调一致

现在,让我们把场景切换到一群舞蹈演员排练舞蹈。为了确保他们完美地同步,需要有一个方法让他们在特定的点上集合并等待,直到所有人都准备好。CyclicBarrier 就扮演了这个集合点的角色。

CyclicBarrier 与 CountDownLatch 类似,但它有一个关键的区别:它可以循环使用。这意味着,当所有线程到达屏障时,屏障会重置,允许线程继续执行。就像舞蹈演员在完成一个动作后,又回到开始位置准备下一个动作一样。

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {

    public static void main(String[] args) throws InterruptedException {
        // 创建一个 CyclicBarrier,指定等待线程数为 3
        CyclicBarrier barrier = new CyclicBarrier(3);

        // 创建 3 个线程,每个线程都将到达屏障并等待
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 抵达屏障");
                try {
                    barrier.await(); // 等待其他线程到达屏障
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        // 主线程等待所有线程完成任务
        System.out.println("所有线程已同步完成任务");
    }
}

应用场景

CountDownLatch 和 CyclicBarrier 在并发编程中都有着广泛的应用。以下是几个常见的场景:

  • CountDownLatch:
    • 等待所有线程完成初始化
    • 等待所有线程完成任务,再关闭程序
    • 等待所有线程从数据库中读取数据,再进行后续处理
  • CyclicBarrier:
    • 多个线程同时到达某个点后,再继续执行
    • 多个线程同时完成任务后,再执行后续任务
    • 多个线程轮流执行任务,确保任务的顺序性

总结

CountDownLatch 和 CyclicBarrier 是并发编程中必不可少的工具,它们可以确保线程协同工作,避免数据不一致和死锁。通过了解它们的特性和应用场景,我们可以高效地协调多线程任务,打造出健壮可靠的并发系统。

常见问题解答

  1. CountDownLatch 和 CyclicBarrier 有什么区别?

CountDownLatch 等待所有线程完成任务后唤醒,而 CyclicBarrier 允许线程循环等待,直至所有人都到达指定点。

  1. 什么时候应该使用 CountDownLatch?

当我们需要等待所有线程完成任务时,可以使用 CountDownLatch。例如,在关闭程序或处理从数据库读取的数据之前。

  1. 什么时候应该使用 CyclicBarrier?

当我们需要多个线程同步执行任务或轮流执行任务时,可以使用 CyclicBarrier。例如,在需要协调多个线程同时到达某个点或执行一系列顺序任务的情况下。

  1. 如何防止线程在等待时发生死锁?

确保所有需要等待的线程都调用了 await() 方法。如果没有所有线程调用 await(),CountDownLatch 或 CyclicBarrier 可能会一直等待,导致死锁。

  1. 这些工具在并发编程中有哪些优势?

CountDownLatch 和 CyclicBarrier 提供了一种简单有效的方式来协调线程,避免数据不一致,并提高并发系统的可靠性。