Java 并发工具类:CountDownLatch 和 CyclicBarrier——线程同步的“协作利器”

引言:为什么多线程协作需要“协调员”?

在Java多线程开发中,我们经常遇到这样的场景:

  • 主线程需要等待所有子线程完成数据加载后,才能汇总结果;
  • 多个子线程需要“齐步走”,例如游戏中所有玩家准备完成后,才能开始游戏;
  • 多阶段任务处理(如先计算,再汇总,再输出),每一步都需要所有线程完成当前阶段才能进入下一阶段。

传统的synchronizedwait/notify虽然能实现同步,但代码复杂且容易出错。Java并发包(java.util.concurrent)提供的CountDownLatchCyclicBarrier,正是解决这类问题的“协作利器”。本文将从原理到实战,带你彻底掌握这两个工具类。


一、CountDownLatch:倒计时门闩,等待任务完成

1.1 核心概念与原理

CountDownLatch(倒计时门闩)通过一个计数器实现线程同步。初始化时设置计数器值(如count=5),当任意线程完成任务后调用countDown()(计数器减1),其他线程通过await()等待计数器归零。

核心方法

  • CountDownLatch(int count):构造函数,初始化计数器;
  • void countDown():计数器减1(通常由子线程调用);
  • boolean await(long timeout, TimeUnit unit):等待计数器归零(可设置超时);
  • void await():阻塞等待计数器归零(无超时)。

1.2 实战示例:主线程等待所有子线程完成

场景:一个电商系统需要从5个不同的数据源(数据库、缓存、文件等)加载商品数据,主线程必须等待所有数据源加载完成后,才能汇总展示。

(1)代码实现
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 初始化计数器(需要等待5个线程完成)
        CountDownLatch latch = new CountDownLatch(5);

        // 启动5个数据加载线程
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                try {
                    // 模拟数据加载(耗时1~3秒)
                    Thread.sleep((long) (Math.random() * 2000 + 1000));
                    System.out.println(Thread.currentThread().getName() + " 数据加载完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();  // 加载完成,计数器减1
                }
            }, "数据源-" + i).start();
        }

        // 主线程等待所有数据加载完成(最多等待5秒)
        boolean allCompleted = latch.await(5, TimeUnit.SECONDS);
        if (allCompleted) {
            System.out.println("所有数据加载完成,开始汇总...");
        } else {
            System.out.println("超时!部分数据未加载完成");
        }
    }
}
(2)输出结果(示例)
数据源-2 数据加载完成
数据源-1 数据加载完成
数据源-5 数据加载完成
数据源-3 数据加载完成
数据源-4 数据加载完成
所有数据加载完成,开始汇总...
(3)关键说明
  • 计数器初始值为5,每个子线程完成后调用countDown(),计数器减到0时,await()返回;
  • await(long timeout, TimeUnit unit)可避免无限等待(如某个子线程卡死,主线程5秒后超时);
  • 计数器不可重置(归零后无法再次使用),适合“一次性”等待场景。

二、CyclicBarrier:循环屏障,多线程齐步走

2.1 核心概念与原理

CyclicBarrier(循环屏障)允许一组线程相互等待,直到所有线程都到达“屏障点”后,再继续执行后续操作。与CountDownLatch不同,它的计数器可重复使用(通过reset()方法重置),适合多阶段任务同步。

核心方法

  • CyclicBarrier(int parties):构造函数,设置需要等待的线程数(parties);
  • CyclicBarrier(int parties, Runnable barrierAction):构造函数,增加一个屏障触发时的回调(所有线程到达后执行);
  • int await():线程到达屏障点后等待(返回当前线程的到达顺序);
  • void reset():重置屏障(计数器归零,未到达的线程抛出BrokenBarrierException)。

2.2 实战示例:多阶段任务的循环同步

场景:一个分布式计算任务需要分3阶段执行(数据校验→计算→汇总),每个阶段必须等待所有计算节点完成当前阶段,才能进入下一阶段。

(1)代码实现
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 定义3个计算节点,屏障触发时执行阶段总结
        CyclicBarrier barrier = new CyclicBarrier(3, () -> 
            System.out.println("所有节点完成当前阶段,进入下一阶段...")
        );

        // 启动3个计算节点线程
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                try {
                    // 阶段1:数据校验
                    System.out.println(Thread.currentThread().getName() + " 完成数据校验");
                    barrier.await();  // 等待其他节点完成阶段1

                    // 阶段2:执行计算
                    System.out.println(Thread.currentThread().getName() + " 完成计算");
                    barrier.await();  // 等待其他节点完成阶段2

                    // 阶段3:结果汇总
                    System.out.println(Thread.currentThread().getName() + " 完成汇总");
                    barrier.await();  // 等待其他节点完成阶段3
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "计算节点-" + i).start();
        }
    }
}
(2)输出结果(示例)
计算节点-1 完成数据校验
计算节点-2 完成数据校验
计算节点-3 完成数据校验
所有节点完成当前阶段,进入下一阶段...
计算节点-1 完成计算
计算节点-2 完成计算
计算节点-3 完成计算
所有节点完成当前阶段,进入下一阶段...
计算节点-1 完成汇总
计算节点-2 完成汇总
计算节点-3 完成汇总
所有节点完成当前阶段,进入下一阶段...
(3)关键说明
  • 屏障初始等待线程数为3,每个线程执行到await()时会阻塞,直到3个线程都到达;
  • 每次所有线程到达后,触发barrierAction(阶段总结),然后计数器重置,支持下一阶段使用;
  • 若某个线程在await()时被中断或超时,会抛出BrokenBarrierException,其他线程也会终止等待。

三、CountDownLatch vs CyclicBarrier:核心差异与选择指南

特性CountDownLatchCyclicBarrier
计数器方向递减(初始值→0)递增(0→parties)
可重置性不可重置(一次性使用)可重置(通过reset()重复使用)
等待线程角色一个/多个线程等待其他线程完成多个线程互相等待(齐步走)
典型场景主线程等待子任务完成(如数据汇总)多阶段任务同步(如分阶段计算)
回调支持无(需手动触发)有(所有线程到达后执行barrierAction

选择建议

  • 若只需“一次性”等待子任务完成(如初始化资源),选CountDownLatch
  • 若需多阶段任务同步(如游戏加载→战斗→结算),选CyclicBarrier
  • 若需要“等待N个线程完成”且支持重复使用,优先CyclicBarrier

四、避坑指南:常见错误与解决方案

4.1 CountDownLatch的“计数器溢出”

错误场景:初始化计数器为5,但实际调用了6次countDown(),导致计数器变为-1(无异常,但await()会立即返回)。
解决方案:确保countDown()调用次数不超过初始值(可通过日志监控调用次数)。

4.2 CyclicBarrier的“屏障损坏”

错误场景:某个线程在await()时被中断,导致屏障状态变为BROKEN,其他线程调用await()时抛出BrokenBarrierException
解决方案

  • 捕获BrokenBarrierException并处理(如重试或终止任务);
  • 使用reset()重置屏障(需确保所有线程已终止)。

4.3 混合使用导致的死锁

错误场景:在CyclicBarrierbarrierAction中再次调用await(),导致线程无法释放。
解决方案barrierAction应是一个轻量级操作(如日志记录、状态更新),避免阻塞。


五、实战扩展:结合线程池的高效协作

实际开发中,CountDownLatchCyclicBarrier常与线程池(如ExecutorService)结合使用,提升资源利用率。

5.1 示例:线程池+CountDownLatch实现批量任务

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolWithLatch {
    public static void main(String[] args) throws InterruptedException {
        int taskCount = 10;
        CountDownLatch latch = new CountDownLatch(taskCount);
        ExecutorService pool = Executors.newFixedThreadPool(3);  // 3个线程处理10个任务

        for (int i = 0; i < taskCount; i++) {
            pool.submit(() -> {
                try {
                    Thread.sleep(500);  // 模拟任务执行
                    System.out.println(Thread.currentThread().getName() + " 完成任务");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();  // 等待所有任务完成
        pool.shutdown();
        System.out.println("所有任务完成,线程池关闭");
    }
}

5.2 输出结果(示例)

pool-1-thread-1 完成任务
pool-1-thread-2 完成任务
pool-1-thread-3 完成任务
...(重复输出)
所有任务完成,线程池关闭

结语:并发协作的“黄金搭档”

CountDownLatchCyclicBarrier是Java并发包中解决线程同步问题的核心工具。前者适合“一次性等待”场景(如主线程等待子任务),后者适合“多阶段齐步走”场景(如分阶段计算)。掌握它们的核心差异和使用技巧,能让你在多线程开发中高效解决协作问题,避免死锁和资源浪费。

下一次遇到多线程同步需求时,不妨问自己:“用CountDownLatch还是CyclicBarrier更合适?”——这可能是优化并发性能的关键一步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张在编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值