知识模块
☕ Java 知识模块
三、Java 并发编程
CountDownLatch 与 CyclicBarrier

CountDownLatch 与 CyclicBarrier

两者都是 JUC 包中的同步工具,用于协调线程执行。

CountDownLatch

作用:一个线程等待其他线程完成。计数器递减,归零后释放等待线程。

构造方法

// 计数器初始值为 N
CountDownLatch latch = new CountDownLatch(3);

主要方法

方法说明
countDown()计数减 1
await()阻塞直到计数为 0
await(timeout, unit)超时等待
getCount()获取当前计数

典型场景:主线程等待子线程

public class CountDownLatchDemo {
    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(3);
        
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    // 执行任务
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();  // 计数减 1
                }
            }).start();
        }
        
        latch.await();  // 阻塞,直到计数为 0
        System.out.println("所有子线程完成,主线程继续");
    }
}

典型场景:服务启动检测

public class ServiceStartup {
    private CountDownLatch latch = new CountDownLatch(3);
    
    public void start() throws Exception {
        // 启动数据库连接
        new Thread(() -> {
            initDatabase();
            latch.countDown();
        }).start();
        
        // 启动缓存服务
        new Thread(() -> {
            initCache();
            latch.countDown();
        }).start();
        
        // 启动消息队列
        new Thread(() -> {
            initMQ();
            latch.countDown();
        }).start();
        
        // 等待所有服务就绪
        latch.await(30, TimeUnit.SECONDS);
        System.out.println("所有服务启动完成");
    }
}

CyclicBarrier

作用:线程互相等待,都到达屏障后一起继续执行。可循环使用。

构造方法

// 屏障点线程数
CyclicBarrier barrier = new CyclicBarrier(3);
 
// 带栅栏动作
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障");
});

主要方法

方法说明
await()到达屏障并等待
await(timeout, unit)超时等待
reset()重置屏障
getNumberWaiting()获取等待线程数
isBroken()屏障是否被破坏

典型场景:分阶段任务

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int parties = 3;
        CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
            System.out.println("=== 阶段完成 ===");
        });
        
        for (int i = 0; i < parties; i++) {
            new Thread(() -> {
                try {
                    for (int phase = 1; phase <= 3; phase++) {
                        System.out.println(Thread.currentThread().getName() + " 阶段" + phase + " 执行");
                        Thread.sleep(500);
                        barrier.await();  // 等待其他线程
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出:

Thread-0 阶段1 执行
Thread-1 阶段1 执行
Thread-2 阶段1 执行
=== 阶段完成 ===
Thread-0 阶段2 执行
Thread-1 阶段2 执行
Thread-2 阶段2 执行
=== 阶段完成 ===
...

典型场景:批量处理

public class BatchProcessor {
    private final CyclicBarrier barrier;
    private final List<Data> dataList;
    private final List<Result> results = new CopyOnWriteArrayList<>();
    
    public BatchProcessor(int parties, List<Data> dataList) {
        this.dataList = dataList;
        this.barrier = new CyclicBarrier(parties, this::saveBatch);
    }
    
    public void process() {
        int batchSize = dataList.size() / barrier.getParties();
        
        for (int i = 0; i < barrier.getParties(); i++) {
            int start = i * batchSize;
            int end = Math.min(start + batchSize, dataList.size());
            
            int finalI = i;
            new Thread(() -> {
                for (int j = start; j < end; j++) {
                    results.add(process(dataList.get(j)));
                }
                try {
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    private void saveBatch() {
        // 保存结果到数据库
        System.out.println("保存 " + results.size() + " 条结果");
    }
}

两者对比

特性CountDownLatchCyclicBarrier
作用一个线程等待其他线程线程互相等待
重用性一次性,不可重置可循环使用
计数方向递减(countDown)递增(await)
触发条件计数归零达到指定线程数
等待者调用 await 的线程所有参与线程
异常影响不影响其他线程一个中断,全抛异常
典型场景主线程等子线程完成分阶段并行任务

选择建议

场景推荐
主线程等待 N 个子任务完成CountDownLatch
N 个线程分阶段执行CyclicBarrier
只需要执行一次CountDownLatch
需要循环执行多次CyclicBarrier
任务间无协作CountDownLatch
任务间需要同步点CyclicBarrier

常见面试题

Q1: CountDownLatch 为什么不能重用?

设计目的不同。CountDownLatch 用于一次性事件,如服务启动等待。如果需要重用,应选择 CyclicBarrier。

Q2: CyclicBarrier 如何处理异常?

一个线程中断或超时,屏障会被破坏,所有等待线程抛出 BrokenBarrierException:

try {
    barrier.await(5, TimeUnit.SECONDS);
} catch (BrokenBarrierException | TimeoutException e) {
    barrier.reset();  // 重置屏障
}

Q3: 计数器和屏障数量如何设置?

  • CountDownLatch:计数 = 需要等待的线程数
  • CyclicBarrier:parties = 参与协作的线程数

Q4: 可以在 CountDownLatch 中使用多个 await 吗?

可以,多个线程可以同时 await,计数归零后全部释放。

小结

  • CountDownLatch:一次性,一个线程等待其他线程,计数递减
  • CyclicBarrier:可循环,线程互相等待,计数递增
  • 选择依据:是否需要重用、是单向等待还是互相等待