请比较 CountDownLatch 类与 CyclicBarrier 类的区别和联系。

参考回答**

CountDownLatchCyclicBarrier 都是 Java 并发包 (java.util.concurrent) 提供的线程同步工具类,它们的主要作用是协调多个线程的执行顺序。但两者的设计目的和使用方式有所不同。

特性 CountDownLatch CyclicBarrier
作用 一个线程等待多个线程完成后再继续执行。 多个线程相互等待,直到所有线程都到达屏障后再继续执行。
重用性 一次性 使用,计数器不能重置。 可重用,屏障可以在所有线程到达后重置并重新使用。
触发条件 计数器通过 countDown() 逐步减为 0。 所有线程调用 await() 并到达屏障。
线程数量控制 计数器值由开发者设置,线程数量可以动态变化,与任务数量无关。 线程数固定,线程总数由 CyclicBarrier 的构造参数决定。
主要用途 任务分解与汇总,例如主线程等待子线程完成任务。 线程阶段性同步,例如并发任务分阶段执行。
可选回调机制 无。 提供 屏障操作Runnable),在所有线程到达屏障时触发。

详细讲解与比较

1. CountDownLatch 的工作原理与使用

  • 原理
    1. CountDownLatch 通过一个计数器控制线程的同步,计数器的初始值由构造函数指定。
    2. 每个线程在完成任务后调用 countDown() 方法,使计数器减 1。
    3. 主线程调用 await(),一直等待直到计数器减为 0,然后继续执行。
  • 适用场景
    • 一个线程等待多个线程完成任务后再执行。
    • 控制操作顺序,比如主线程在所有依赖任务完成后开始执行。

示例:任务分解与汇总

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " completed task.");
                latch.countDown(); // 计数器减 1
            }).start();
        }

        latch.await(); // 等待计数器减为 0
        System.out.println("All tasks completed. Main thread continues.");
    }
}

输出示例

Thread-0 completed task.
Thread-1 completed task.
Thread-2 completed task.
All tasks completed. Main thread continues.

2. CyclicBarrier 的工作原理与使用

  • 原理
    1. CyclicBarrier 是一个循环屏障,线程通过调用 await() 方法进入等待状态。
    2. 当所有线程都调用 await() 并到达屏障时,屏障打开,所有线程继续执行。
    3. 支持重用:屏障会自动重置,可在下一轮任务中再次使用。
  • 适用场景
    • 多线程协同工作时,需要线程在某些阶段同步。
    • 例如并发任务分阶段执行,每个阶段所有线程需要同步后再开始下一阶段。

示例:多线程分阶段同步

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int threadCount = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("All threads reached the barrier. Proceeding to next phase.");
        });

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " reached the barrier.");
                    barrier.await(); // 等待其他线程到达屏障
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出示例

Thread-0 reached the barrier.
Thread-1 reached the barrier.
Thread-2 reached the barrier.
All threads reached the barrier. Proceeding to next phase.

3. CountDownLatch 与 CyclicBarrier 的主要区别

特性 CountDownLatch CyclicBarrier
是否可重用 一次性使用,计数器不能重置。 可重用,屏障在所有线程到达后自动重置。
线程等待的条件 主线程通过 await() 等待计数器减为 0。 所有线程通过 await() 等待其他线程到达屏障。
使用场景 一个线程等待多个线程完成任务。 多个线程相互等待,分阶段协作。
是否有回调机制 无。 支持屏障操作(Runnable 回调),所有线程到达屏障时触发。
线程数量的灵活性 与任务数量一致,线程数量可以动态变化。 固定线程数量,由 CyclicBarrier 构造时指定。

4. 使用场景对比

场景 推荐工具 原因
主线程等待多个子线程完成任务 CountDownLatch 主线程通过 await() 等待所有子线程完成任务后继续执行。
多线程分阶段同步执行 CyclicBarrier 多线程需要在每个阶段同步后再继续执行下一阶段。
单次任务调度 CountDownLatch 计数器一次性使用,适合单次任务调度。
可重复使用的屏障 CyclicBarrier 屏障可重用,适合需要多轮任务协作的场景。

5. 联系与补充

  1. 实现原理
    • 两者都基于 AQS(AbstractQueuedSynchronizer) 实现线程同步。
    • CountDownLatch 使用共享模式来减少计数器。
    • CyclicBarrier 使用独占模式实现线程屏障。
  2. 组合使用
    • CountDownLatchCyclicBarrier 可以结合使用,例如主线程等待多个阶段的执行结果。

总结

工具类 特点
CountDownLatch – 主线程等待多个子线程完成任务。- 一次性使用,计数器不可重置。
CyclicBarrier – 多个线程相互等待,分阶段协作。- 支持回调和重用,适合循环使用。

在实际开发中,应根据任务的需求选择合适的工具:

  • 使用 CountDownLatch 适合一次性任务汇总。
  • 使用 CyclicBarrier 适合需要多阶段、循环同步的场景。

发表评论

后才能评论