Java CyclicBarrier案例

wen java案例 3

Java并发编程实战:CyclicBarrier核心原理与高并发场景案例深度解析


目录导读

  1. CyclicBarrier是什么? —— 从“人齐了才能开饭”说起
  2. 核心机制剖析 —— 计数器、屏障点与线程间通信
  3. 实战案例一:多线程数据分片汇总(模拟报表生成)
  4. 实战案例二:CyclicBarrier + 线程池实现“分段压力测试”
  5. CyclicBarrier vs CountDownLatch —— 面试必问的5个区别
  6. 易踩坑与性能优化 —— 超时、中断、重用陷阱
  7. 高频问答(FAQ) —— 结合搜索引擎热点问题解答

CyclicBarrier是什么?

想象一个场景:公司团建要开饭,必须等所有人到齐才动筷子,Java中的CyclicBarrier正是这种“人齐了再执行”的同步工具,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(Barrier Point),然后才继续执行后续任务。
其构造函数为:CyclicBarrier(int parties, Runnable barrierAction),其中parties表示线程数,barrierAction是当所有线程到达后优先执行的任务(可选)。

Java CyclicBarrier案例


核心机制剖析

  • 计数器与重置:内部计数器初始为parties,每有一个线程调用await(),计数器减1,当计数归零时,屏障被打破,所有等待线程被唤醒,同时计数器自动重置为初始值——这就是“循环”(Cyclic)的含义。
  • 线程状态:等待中的线程会被阻塞,直到所有线程到达,若某个线程被中断或超时,则屏障被破坏,抛出BrokenBarrierException,其他线程也会收到异常。
  • 内存一致性:线程在await()之前的所有写操作,在其他线程通过屏障后是可见的(遵循happens-before规则)。

实战案例一:多线程数据分片汇总

场景:金融系统需要计算100万条交易记录的总金额,为了提升效率,将数据分成10个分片,每个线程计算一个分片,最后汇总。

public class DataAggregateTask {
    private static final int PARTIES = 10;
    private static final ConcurrentHashMap<String, Long> resultMap = new ConcurrentHashMap<>();
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(PARTIES, () -> {
            // 所有分片计算完成后,汇总
            long total = resultMap.values().stream().mapToLong(Long::longValue).sum();
            System.out.println("汇总金额:" + total);
        });
        for (int i = 0; i < PARTIES; i++) {
            final int threadId = i;
            new Thread(() -> {
                long sum = calculatePart(threadId);
                resultMap.put("part-" + threadId, sum);
                try {
                    barrier.await(); // 等待其他线程完成
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

要点barrierAction在最后一个线程到达时执行,确保汇总逻辑只执行一次。


实战案例二:CyclicBarrier + 线程池的“循环复用”

场景:对API接口进行多轮并发压测,每轮500并发,共执行3轮,每轮开始前统一释放压力。

ExecutorService executor = Executors.newFixedThreadPool(500);
CyclicBarrier barrier = new CyclicBarrier(500, () -> {
    System.out.println("第" + (++round) + "轮压测开始,时间:" + System.currentTimeMillis());
});
for (int i = 0; i < 1500; i++) { // 3轮 * 500
    executor.submit(() -> {
        try {
            barrier.await(); // 500个线程同时到达后,同时释放
            httpRequest(); // 并发调用
        } catch (Exception e) {}
    });
}

优势:CyclicBarrier天然支持“重复使用”,在压测场景中省去重新初始化的开销。


CyClicBarrier vs CountDownLatch(面试高频)

对比维度 CyclicBarrier CountDownLatch
可重用性 可循环使用,计数器自动重置 一次性,计数归零后失效
使用场景 多个线程互相等待,再齐步走 一个或多个线程等待其他线程完成
计数方式 等待方调用await()减1 完成方调用countDown()减1
屏障破坏 支持超时/中断,会抛BrokenBarrierException 无破坏概念
API复杂度 支持barrierAction,更灵活 简单直接

易踩坑与性能优化

  • 陷阱1:循环引用导致死锁 —— 如果barrierAction中又调用了await(),会永久阻塞。
  • 陷阱2:线程池线程数 < parties —— 若核心线程数小于parties,会导致线程永远等不到齐,必须用newFixedThreadPool(parties)
  • 优化:结合await(long timeout, TimeUnit unit)防止无限等待;使用reset()重置屏障(但注意会抛异常)。

高频问答(FAQ)

Q1:CyclicBarrier能实现CountDownLatch吗?
答:不能完全替代,CountDownLatch是一次性的,而CyclicBarrier是循环的,若硬要用CyclicBarrier模拟一次性,需要额外逻辑控制重置。

Q2:CyclicBarrier和Semaphore的区别?
答:Semaphore控制同时访问的线程数(信号量),而CyclicBarrier控制线程之间相互等待的同步点。

Q3:屏障破坏后,其他线程会怎么样?
答:它们会立即抛出BrokenBarrierException,需要根据业务逻辑决定是否重试或终止。

Q4:性能比CountDownLatch差吗?
答:差别微乎其微,主要取决于JVM的锁竞争程度,两者性能差异小于5%。

抱歉,评论功能暂时关闭!