CountDownLatch/CyclicBarrier 区别

 CountDownLatch 和 CyclicBarrier 这两个 Java 并发工具类的核心区别,我会从定义、使用场景、核心特性等方面帮你清晰梳理。
 

一、核心概念与本质区别

先通过简单的比喻理解两者:
 
  • CountDownLatch:像一场考试的 “交卷倒计时”,监考老师(主线程)要等所有学生(子线程)都交卷(执行完任务)才能收卷,倒计时归 0 后就结束,只能使用一次。
  • CyclicBarrier:像一群人约好 “集齐再出发” 的旅游团,所有人到齐(线程都到达屏障点)后才能一起出发(执行后续任务),出发后屏障可以重置,能重复使用。

二、详细区别对比

 
特性CountDownLatchCyclicBarrier
核心作用 一个 / 多个线程等待其他线程完成操作 多个线程互相等待,直到所有线程都到达屏障点
可复用性 计数器归 0 后无法重置,只能用一次 屏障触发后可自动重置,能重复使用
触发条件 计数器 countDown() 到 0 触发 指定数量的线程调用 await() 到达屏障触发
等待方 可以是一个或多个线程等待 参与的线程互相等待(都是等待方)
额外功能 无回调函数 支持屏障触发时执行指定的回调任务
底层实现 基于 AQS 的共享模式 基于 ReentrantLock + Condition

三、代码示例(直观理解)

1. CountDownLatch 示例(一次性等待)

场景:主线程等待 3 个工作线程全部完成任务后,再执行汇总操作。
 
java
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 初始化计数器为 3(对应 3 个工作线程)
        CountDownLatch latch = new CountDownLatch(3);

        // 启动 3 个工作线程
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 执行任务中...");
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 任务完成,计数器减 1
                    System.out.println(Thread.currentThread().getName() + " 任务完成,计数器剩余: " + latch.getCount());
                }
            }, "线程" + i).start();
        }

        // 主线程等待计数器归 0
        latch.await();
        System.out.println("所有工作线程任务完成,主线程执行汇总操作");
    }
}
输出(顺序可能略有不同):
plaintext
线程1 执行任务中...
线程2 执行任务中...
线程3 执行任务中...
线程1 任务完成,计数器剩余: 2
线程2 任务完成,计数器剩余: 1
线程3 任务完成,计数器剩余: 0
所有工作线程任务完成,主线程执行汇总操作

2. CyclicBarrier 示例(可重复使用)

场景:3 个运动员线程,每次都要等所有人到起跑线(屏障点)才能一起起跑,且支持重复比赛(屏障重置)。
 
java
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 初始化屏障:3 个线程到达后触发,且触发时执行回调任务(打印起跑提示)
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("===== 所有运动员已就位,开始起跑!=====\n");
        });

        // 定义运动员任务
        Runnable runner = () -> {
            try {
                for (int i = 1; i <= 2; i++) { // 模拟 2 轮比赛
                    System.out.println(Thread.currentThread().getName() + " 到达起跑线,等待其他运动员(第" + i + "轮)");
                    barrier.await(); // 到达屏障,等待其他线程
                    System.out.println(Thread.currentThread().getName() + " 开始跑步(第" + i + "轮)");
                    Thread.sleep(500); // 模拟跑步耗时
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        // 启动 3 个运动员线程
        new Thread(runner, "运动员A").start();
        new Thread(runner, "运动员B").start();
        new Thread(runner, "运动员C").start();
    }
}
 
输出:
 
plaintext
运动员A 到达起跑线,等待其他运动员(第1轮)
运动员B 到达起跑线,等待其他运动员(第1轮)
运动员C 到达起跑线,等待其他运动员(第1轮)
===== 所有运动员已就位,开始起跑!=====

运动员C 开始跑步(第1轮)
运动员A 开始跑步(第1轮)
运动员B 开始跑步(第1轮)
运动员A 到达起跑线,等待其他运动员(第2轮)
运动员B 到达起跑线,等待其他运动员(第2轮)
运动员C 到达起跑线,等待其他运动员(第2轮)
===== 所有运动员已就位,开始起跑!=====

运动员C 开始跑步(第2轮)
运动员B 开始跑步(第2轮)
运动员A 开始跑步(第2轮)

四、使用场景总结

  • CountDownLatch 适合:
    1. 主线程等待多个子线程完成初始化 / 任务后,再执行后续逻辑(如程序启动时加载多个配置文件);
    2. 多个子线程等待主线程发出 “开始” 信号后同时执行(可初始化 latch 为 1,主线程调用 countDown () 释放所有等待的子线程)。
     
  • CyclicBarrier 适合:
    1. 多个线程需要同步步调、分批执行任务的场景(如多线程分批处理数据,每批都要等所有线程准备好);
    2. 重复的同步场景(如定时任务中,每次都要等所有线程就绪后再执行)。

总结

  1. 核心差异:CountDownLatch 是 “单向倒计时,一次性使用”,侧重 “等待其他线程完成”;CyclicBarrier 是 “互相等待,可重复使用”,侧重 “所有线程同步出发”。
  2. 复用性:CountDownLatch 计数器归 0 后失效,CyclicBarrier 触发后自动重置,可循环使用。
  3. 回调能力:CyclicBarrier 支持屏障触发时执行回调任务,CountDownLatch 无此功能。
posted @ 2026-01-26 15:51  野鹤闲人  阅读(19)  评论(0)    收藏  举报