CyclicBarrier深度解析:Java中的“循环栅栏“同步工具

在Java并发编程中,我们经常需要让一组线程"集齐后再一起行动 "------比如多个线程完成各自任务后,统一进入下一个阶段。这时候就需要用到 CyclicBarrier(循环栅栏) 这个同步工具类。它就像一个可重复使用的"集合点",让指定数量的线程都到达这里后,再一起继续执行后续逻辑。

一、 生活中的CyclicBarrier:同学聚会的故事

想象这样一个场景:小明、小美、小华、小丽四位老同学相约聚餐:

  • 他们各自从不同地方出发
  • 每个人到达餐厅的时间不一样
  • 但约定:必须所有人都到了才能开始点菜

这就是CyclicBarrier的现实比喻

  • 每个同学 = 一个线程
  • 餐厅 = 屏障点(Barrier)
  • 点菜 = 屏障点后的后续操作
  • 约定 = 所有线程必须到达屏障点才能继续执行

二、 什么是CyclicBarrier?

1. 官方定义

CyclicBarrier是Java并发包(JUC)中提供的一个同步辅助工具 ,允许一组线程相互等待,直到所有线程都到达某个公共屏障点,然后屏障打开,所有线程继续执行。

2. 核心特点

  • 循环使用:不像CountDownLatch只能使用一次,CyclicBarrier可以重复使用
  • 多线程协作:多个线程在屏障点同步,然后一起继续执行
  • 可选的屏障动作:所有线程到达屏障点时,可以执行一个预定义的任务

3. 形象比喻:团队开发中的"代码审查会议"

bash 复制代码
// 团队成员:A、B、C、D
// 会议规则:所有人必须完成代码后才能开始评审

CyclicBarrier codeReviewMeeting = new CyclicBarrier(4, () -> {
    System.out.println("所有人都已完成代码,开始代码评审会议!");
});

// 每个成员开发代码(线程执行)
memberA.startCoding();  // 耗时不同
memberB.startCoding();  // 耗时不同
// ...

// 所有成员完成代码后,自动触发评审会议

三、 CyclicBarrier的工作原理

1. 核心类结构

bash 复制代码
public class CyclicBarrier {
    // 主要属性
    private final ReentrantLock lock = new ReentrantLock();      // 锁,保证线程安全
    private final Condition trip = lock.newCondition();          // 条件变量,线程等待
    private final int parties;                                   // 需要等待的线程数
   
    private final Runnable barrierCommand;                             // 屏障触发时执行的任务
    private Generation generation = new Generation();           // 当前"代"
    private int count;                                           // 当前剩余等待线程数
    
    // 内部类:表示屏障的"一代"
    private static class Generation {
        boolean broken = false;  // 屏障是否被破坏
    }
}

2. 构造函数详解

bash 复制代码
// 构造函数1:指定需要等待的线程数(必须>0,否则报错)
public CyclicBarrier(int parties) {
    this(parties, null);  // 第二个参数为null,表示没有屏障动作
}

// 构造函数2:指定线程数+栅栏打开前的回调任务
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    
    this.parties = parties;        // 需要等待的线程总数
    this.count = parties;          // 当前剩余等待数(初始等于总数)
    this.barrierCommand = barrierAction;  // 屏障打开时执行的任务
}

参数说明

  • parties :需要到达屏障的线程数量
  • barrierAction :所有线程到达后优先执行的任务(可选)

3. 核心方法:await()

线程执行完自己的任务后,调用await()方法,就表示"我已经到达栅栏了 ",随后线程会阻塞,直到所有线程都调用了await()

bash 复制代码
// 方法1:无限期等待,直到所有线程到达
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe);  // 不会发生,因为不是定时等待
    }
}

// 方法2:带超时的等待:超过指定时间还没等齐,就抛出超时异常,不再等待
public int await(long timeout, TimeUnit unit) 
    throws InterruptedException, BrokenBarrierException, TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

关键说明:

  • BrokenBarrierException :栅栏被"破坏"时抛出。比如其中一个线程在等待时被中断、超时,或者调用了reset()方法重置栅栏,都会导致栅栏破坏,其他等待的线程会抛出这个异常;
  • 返回值await()会返回当前线程到达栅栏的"顺序号"(从0开始,最后一个到达的线程返回0,其他线程返回1, 2, 3...(按到达顺序));
  • 线程安全await()方法内部通过保证线程安全,多个线程同时调用不会出现计数错误。

4. 核心逻辑:dowait()方法解析

CyclicBarrier的核心逻辑都在dowait()方法里(两个await()最终都调用它)。

bash 复制代码
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    // 获取锁
    final ReentrantLock lock = this.lock;
    lock.lock();// 加锁,保证线程安全
    
    try {
        final Generation g = generation;
        
        // 1. 检查屏障是否已被破坏// 加锁,保证线程安全
        if (g.broken)
            throw new BrokenBarrierException();
        
        // 2. 检查当前线程是否被中断,如果是则破坏栅栏、唤醒所有线程并抛异常
        if (Thread.interrupted()) {
            breakBarrier();  // 破坏屏障,唤醒其他线程
            throw new InterruptedException();
        }
        
        // 3. 计数器减1(每有一个线程到达,计数就减1)
        int index = --count;
        
        // 4. 如果计数器为0,说明所有线程都已到达
        if (index == 0) {
            boolean ranAction = false;
            try {
                // 执行屏障任务(如果设置了)
                final Runnable command = barrierCommand;
                if (command != null) {
                    command.run();
                }
                ranAction = true;
                
                // 唤醒所有等待线程,重置屏障(下一代)
                nextGeneration();
                return 0;
            } finally {
                // 确保屏障任务异常时也能唤醒其他线程
                if (!ranAction) {
                    breakBarrier();
                }
            }
        }
        
        // 5. 计数器不为0,线程进入等待状态
        for (;;) {
            try {
                if (!timed) {
                    // 无限期等待
                    trip.await();
                } else if (nanos > 0L) {
                    // 带超时等待
                    nanos = trip.awaitNanos(nanos);
                }
            } catch (InterruptedException ie) {
                // 等待期间被中断,破坏栅栏并抛异常
                if (g == generation && !g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    Thread.currentThread().interrupt();
                }
            }
            
            // 等待中被唤醒后,检查栅栏状态:破坏则抛异常,重置则返回顺序号,超时则破坏栅栏
            if (g.broken)
                throw new BrokenBarrierException();
            
            // 检查是否已经换代
            if (g != generation)
                return index;
            
            // 检查是否超时
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();// 解锁
    }
}

核心逻辑总结

  1. 线程到齐前:每个线程调用await() → 计数器减1 → 进入等待;
  2. 所有线程到齐:计数器减为0 → 执行回调任务(可选)→ 重置栅栏(计数器恢复为初始值)→ 唤醒所有等待线程;
  3. 异常情况:线程中断、超时 → 破坏栅栏 → 唤醒所有线程 → 抛出异常。

四、 实战代码示例

1. 基础示例:同学聚餐

bash 复制代码
import java.util.concurrent.*;

/**
 * CyclicBarrier示例:同学聚餐
 */
public class CyclicBarrierDemo {
    
    public static void main(String[] args) {
        // 1. 创建线程池(模拟4个同学各自出发去餐厅)
        ExecutorService executor = Executors.newFixedThreadPool(4);
        
        // 2. 初始化CyclicBarrier:4个线程(4个同学)+ 回调任务(呼叫服务员)
        CyclicBarrier barrier = new CyclicBarrier(4, () -> {
            System.out.println("═══════════════════════════════");
            System.out.println("所有同学都已到达,开始点餐!");
            System.out.println("═══════════════════════════════");
        });
        
        // 同学名单
        String[] classmates = {"小明", "小美", "小华", "小丽"};
        
        // 3. 创建并执行任务
        for (String name : classmates) {
            executor.execute(new DinnerTask(name, barrier));
        }
        
        // 4.关闭线程池(任务提交完后关闭,不影响已执行的线程)
        executor.shutdown();
    }
    
    /**
     * 聚餐任务
     */
    static class DinnerTask implements Runnable {
        private final String name;
        private final CyclicBarrier barrier;
        private static final Random random = new Random();
        
        public DinnerTask(String name, CyclicBarrier barrier) {
            this.name = name;
            this.barrier = barrier;
        }
        
        @Override
        public void run() {
            try {
                // 模拟前往餐厅的路程时间
                int travelTime = random.nextInt(5) + 1;  // 1-5秒
                System.out.println(name + "出发前往餐厅,预计需要" + travelTime + "秒");
                Thread.sleep(travelTime * 1000L);
                
                System.out.println(name + "到达餐厅,等待其他同学...");
                
                // 调用await(),等待其他同学(到达栅栏,开始阻塞)
                int arrivalOrder = barrier.await();
                
                // 所有同学到达后继续执行
                System.out.println(name + "开始点菜(到达顺序:" + arrivalOrder + ")");
                
                // 模拟点菜时间
                Thread.sleep(1000);
                System.out.println(name + "点菜完成");
                
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果示例:

bash 复制代码
小明出发前往餐厅,预计需要4秒
小美出发前往餐厅,预计需要5秒
小华出发前往餐厅,预计需要2秒
小丽出发前往餐厅,预计需要4秒
小华到达餐厅,等待其他同学...
小明到达餐厅,等待其他同学...
小丽到达餐厅,等待其他同学...
小美到达餐厅,等待其他同学...
==================
所有同学都已到达,开始点餐!
==================
小美开始点菜(到达顺序:0)
小华开始点菜(到达顺序:3)
小明开始点菜(到达顺序:2)
小丽开始点菜(到达顺序:1)
小美点菜完成
小明点菜完成
小丽点菜完成
小华点菜完成

结果解读

  1. 4个同学(线程)陆续到达餐厅(执行完"赶路"任务),各自调用await()进入等待;
  2. 最后一个同学(同学3)到达后,触发回调任务(呼叫服务员);
  3. 回调任务执行完,栅栏打开,所有等待的同学(线程)被唤醒,继续执行"点餐"逻辑;
  4. 返回值"到达顺序号":最后一个到达的线程返回0,前面的线程按到达顺序返回3、2、1(从0倒序)。

2. 进阶示例:多阶段数据处理

CyclicBarrier的循环特性使其非常适合多阶段处理场景:

bash 复制代码
/**
 * 多阶段数据处理:分阶段处理一批数据
 */
public class MultiPhaseProcessing {
    
    public static void main(String[] args) {
        int threadCount = 3;  // 3个工作线程
        int phaseCount = 2;   // 2个处理阶段
        
        // 创建CyclicBarrier,每个阶段所有线程完成后执行汇总
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("═══════════════════════════════");
            System.out.println("当前阶段所有线程处理完成,开始汇总...");
            System.out.println("═══════════════════════════════");
        });
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        // 提交任务
        for (int i = 0; i < threadCount; i++) {
            executor.execute(new DataProcessor("处理器-" + (i + 1), barrier, phaseCount));
        }
        
        executor.shutdown();
    }
    
    /**
     * 数据处理器
     */
    static class DataProcessor implements Runnable {
        private final String name;
        private final CyclicBarrier barrier;
        private final int totalPhases;
        
        public DataProcessor(String name, CyclicBarrier barrier, int totalPhases) {
            this.name = name;
            this.barrier = barrier;
            this.totalPhases = totalPhases;
        }
        
        @Override
        public void run() {
            try {
                for (int phase = 1; phase <= totalPhases; phase++) {
                    // 阶段开始
                    System.out.println(name + " 开始第" + phase + "阶段处理");
                    
                    // 模拟数据处理(不同线程处理时间不同)
                    Thread.sleep((long) (Math.random() * 3000));
                    
                    System.out.println(name + " 第" + phase + "阶段处理完成,等待其他处理器...");
                    
                    // 等待其他处理器完成当前阶段
                    barrier.await();
                    
                    // 所有处理器完成当前阶段后继续
                    System.out.println(name + " 进入第" + phase + "阶段后续处理");
                    Thread.sleep(500);
                }
                
                System.out.println(name + " 所有阶段处理完成!");
                
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果示例:

bash 复制代码
处理器1开始第1阶段处理
处理器2开始第1阶段处理
处理器1第1阶段处理完成,等待其他处理器...
处理器2第1阶段处理完成,等待其他处理器...
处理器3开始第1阶段处理
处理器3第1阶段处理完成,等待其他处理器...
═══════════════════════════════
当前阶段所有线程处理完成,开始汇总...
═══════════════════════════════
处理器3 进入第1阶段后续处理
处理器1 进入第1阶段后续处理
处理器2 进入第1阶段后续处理
处理器3开始第2阶段处理
处理器3第2阶段处理完成,等待其他处理器...
处理器2开始第2阶段处理
处理器1开始第2阶段处理
处理器2第2阶段处理完成,等待其他处理器...
处理器1第2阶段处理完成,等待其他处理器...
═══════════════════════════════
当前阶段所有线程处理完成,开始汇总...
═══════════════════════════════
处理器1 进入第2阶段后续处理
处理器2 进入第2阶段后续处理
处理器3 进入第2阶段后续处理
处理器1 所有阶段处理完成!
处理器2 所有阶段处理完成!
处理器3 所有阶段处理完成!

3. 实战应用:批量文件下载器

bash 复制代码
/**
 * 批量文件下载器:多个文件同时下载,全部下载完成后进行合并
 */
public class BatchFileDownloader {
    
    public static void main(String[] args) {
        // 要下载的文件列表
        String[] fileUrls = {
            "http://example.com/file1.zip",
            "http://example.com/file2.zip",
            "http://example.com/file3.zip",
            "http://example.com/file4.zip"
        };
        
        // 创建CyclicBarrier,所有文件下载完成后执行合并操作
        CyclicBarrier barrier = new CyclicBarrier(
            fileUrls.length, 
            () -> System.out.println("\n✅ 所有文件下载完成,开始合并文件...")
        );
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(fileUrls.length);
        
        // 创建下载任务
        List<Future<File>> futures = new ArrayList<>();
        for (int i = 0; i < fileUrls.length; i++) {
            String url = fileUrls[i];
            String fileName = "file_" + (i + 1) + ".zip";
            
            Callable<File> downloadTask = new DownloadTask(url, fileName, barrier);
            futures.add(executor.submit(downloadTask));
        }
        
        // 处理下载结果
        try {
            List<File> downloadedFiles = new ArrayList<>();
            for (Future<File> future : futures) {
                downloadedFiles.add(future.get());
            }
            
            System.out.println("\n🎉 下载任务全部完成,共下载文件:" + downloadedFiles.size() + "个");
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
    
    /**
     * 下载任务
     */
    static class DownloadTask implements Callable<File> {
        private final String url;
        private final String fileName;
        private final CyclicBarrier barrier;
        
        public DownloadTask(String url, String fileName, CyclicBarrier barrier) {
            this.url = url;
            this.fileName = fileName;
            this.barrier = barrier;
        }
        
        @Override
        public File call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName() + " 开始下载:" + fileName);
                
                // 模拟下载时间
                int downloadTime = (int) (Math.random() * 5) + 1;  // 1-5秒
                Thread.sleep(downloadTime * 1000L);
                
                System.out.println(Thread.currentThread().getName() + " 下载完成:" + fileName + 
                                 "(耗时" + downloadTime + "秒)");
                
                // 等待其他下载任务完成
                barrier.await();
                
                // 返回下载的文件(这里模拟创建文件)
                return new File("/downloads/" + fileName);
                
            } catch (Exception e) {
                System.err.println("下载失败:" + fileName + " - " + e.getMessage());
                throw e;
            }
        }
    }
}

执行结果示例:

bash 复制代码
pool-1-thread-1 开始下载:file_1.zip
pool-1-thread-2 开始下载:file_2.zip
pool-1-thread-4 开始下载:file_4.zip
pool-1-thread-3 开始下载:file_3.zip
pool-1-thread-2 下载完成:file_2.zip(耗时1秒)
pool-1-thread-3 下载完成:file_3.zip(耗时2秒)
pool-1-thread-4 下载完成:file_4.zip(耗时4秒)
pool-1-thread-1 下载完成:file_1.zip(耗时5秒)

✅ 所有文件下载完成,开始合并文件...

🎉 下载任务全部完成,共下载文件:4个

五、 核心特性详解

1. 循环使用特性

bash 复制代码
// CyclicBarrier可以重置并重复使用
public class CyclicBarrierReuseDemo {
    
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("════ 屏障打开 ════");
        });
        
        // 第一次使用
        System.out.println("=== 第1轮开始 ===");
        startThreads(barrier, 3);
        Thread.sleep(2000);
        
        // 自动重置,可以再次使用
        System.out.println("\n=== 第2轮开始 ===");
        startThreads(barrier, 3);
        Thread.sleep(2000);
        
        // 甚至可以手动重置
        System.out.println("\n=== 手动重置后第3轮开始 ===");
        barrier.reset();  // 重置屏障
        startThreads(barrier, 3);
    }
    
    private static void startThreads(CyclicBarrier barrier, int count) {
        ExecutorService executor = Executors.newFixedThreadPool(count);
        
        for (int i = 0; i < count; i++) {
            final int threadNum = i + 1;
            executor.execute(() -> {
                try {
                    System.out.println("线程" + threadNum + "到达屏障");
                    barrier.await();
                    System.out.println("线程" + threadNum + "通过屏障");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
    }
}

执行结果示例:

bash 复制代码
=== 第1轮开始 ===
线程3到达屏障
线程1到达屏障
线程2到达屏障
════ 屏障打开 ════
线程2通过屏障
线程1通过屏障
线程3通过屏障

=== 第2轮开始 ===
线程1到达屏障
线程2到达屏障
线程3到达屏障
════ 屏障打开 ════
线程3通过屏障
线程1通过屏障
线程2通过屏障

=== 手动重置后第3轮开始 ===
线程1到达屏障
线程2到达屏障
线程3到达屏障
════ 屏障打开 ════
线程3通过屏障
线程2通过屏障
线程1通过屏障

2. 屏障破坏与恢复

bash 复制代码
// 演示屏障被破坏的情况
public class BrokenBarrierDemo {
    
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(3);
        
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 线程1:正常到达
        executor.execute(() -> {
            try {
                System.out.println("线程1:开始执行");
                Thread.sleep(1000);
                System.out.println("线程1:到达屏障,等待...");
                barrier.await();
                System.out.println("线程1:通过屏障");
            } catch (Exception e) {
                System.out.println("线程1:异常 - " + e.getClass().getSimpleName());
            }
        });
        
        // 线程2:被中断
        executor.execute(() -> {
            try {
                System.out.println("线程2:开始执行");
                Thread.sleep(2000);
                System.out.println("线程2:到达屏障,等待...");
                Thread.currentThread().interrupt();  // 模拟中断
                barrier.await();
                System.out.println("线程2:通过屏障");
            } catch (Exception e) {
                System.out.println("线程2:异常 - " + e.getClass().getSimpleName());
            }
        });
        
        // 线程3:超时
        executor.execute(() -> {
            try {
                System.out.println("线程3:开始执行");
                Thread.sleep(3000);
                System.out.println("线程3:到达屏障,等待(带超时)...");
                barrier.await(1, TimeUnit.SECONDS);  // 1秒超时
                System.out.println("线程3:通过屏障");
            } catch (Exception e) {
                System.out.println("线程3:异常 - " + e.getClass().getSimpleName());
                
                // 检查屏障状态
                System.out.println("屏障是否被破坏:" + barrier.isBroken());
                System.out.println("等待线程数:" + barrier.getNumberWaiting());
            }
        });
        
        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);
        
        // 重置屏障(恢复使用)
        System.out.println("\n重置屏障...");
        barrier.reset();
        System.out.println("重置后屏障是否被破坏:" + barrier.isBroken());
    }
}

执行结果示例:

bash 复制代码
=== 第1轮开始 ===
线程3到达屏障
线程1到达屏障
线程2到达屏障
════ 屏障打开 ════
线程2通过屏障
线程1通过屏障
线程3通过屏障

=== 第2轮开始 ===
线程1到达屏障
线程2到达屏障
线程3到达屏障
════ 屏障打开 ════
线程3通过屏障
线程1通过屏障
线程2通过屏障

=== 手动重置后第3轮开始 ===
线程1到达屏障
线程2到达屏障
线程3到达屏障
════ 屏障打开 ════
线程3通过屏障
线程2通过屏障
线程1通过屏障

3. 监控方法

bash 复制代码
// CyclicBarrier提供了一些监控方法
public class MonitorDemo {
    
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {
            System.out.println("所有线程到达,屏障打开!");
        });
        
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // 启动5个线程
        for (int i = 0; i < 5; i++) {
            final int threadId = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(threadId * 1000L);  // 不同到达时间
                    
                    System.out.println("线程" + threadId + "到达屏障");
                    System.out.println("  当前等待线程数:" + barrier.getNumberWaiting());
                    System.out.println("  屏障是否破坏:" + barrier.isBroken());
                    
                    barrier.await();
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    }
}

执行结果示例:

bash 复制代码
线程0到达屏障
  当前等待线程数:0
  屏障是否破坏:false
线程1到达屏障
  当前等待线程数:1
  屏障是否破坏:false
线程2到达屏障
  当前等待线程数:2
  屏障是否破坏:false
线程3到达屏障
  当前等待线程数:3
  屏障是否破坏:false
线程4到达屏障
  当前等待线程数:4
  屏障是否破坏:false
所有线程到达,屏障打开!

六、 CyclicBarrier vs CountDownLatch

1. 核心区别对比表

特性 CyclicBarrier CountDownLatch
使用次数 可循环使用(reset()) 一次性使用
计数方向 递减到0后自动重置 递减到0后结束
参与者角色 所有线程都是对等的参与者 有明确的等待者和完成者
重用性 支持多次重用 不支持重用
屏障动作 支持(barrierAction) 不支持
典型场景 多线程分阶段处理 一个/多个线程等待其他线程完成

2. 场景对比示例

CountDownLatch场景:裁判等待所有运动员就位

bash 复制代码
// 裁判(主线程)等待所有运动员(工作线程)就位
CountDownLatch latch = new CountDownLatch(5);

// 运动员线程
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        System.out.println("运动员就位");
        latch.countDown();  // 通知裁判我已就位
    }).start();
}

// 裁判线程等待
latch.await();  // 等待所有运动员就位
System.out.println("所有运动员就位,比赛开始!");

CyclicBarrier场景:所有运动员同时起跑

bash 复制代码
// 所有运动员互相等待,同时起跑
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
    System.out.println("所有运动员就位,发令枪响!");
});

// 运动员线程
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        System.out.println("运动员到达起跑线");
        try {
            barrier.await();  // 等待其他运动员
            System.out.println("运动员起跑!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

3. 选择建议

  • 使用CountDownLatch:当一个/多个线程需要等待其他线程完成初始化工作
  • 使用CyclicBarrier:当多个线程需要相互等待,到达某个点后一起继续执行

七、 高级应用场景

1. 分布式任务协调

bash 复制代码
/**
 * 分布式系统模拟:多个节点执行任务,需要同步后继续
 */
public class DistributedTaskCoordinator {
    
    public static void main(String[] args) {
        int nodeCount = 3;
        int phaseCount = 3;
        
        // 模拟分布式节点
        List<Node> nodes = new ArrayList<>();
        for (int i = 0; i < nodeCount; i++) {
            nodes.add(new Node("Node-" + (i + 1)));
        }
        
        // 创建协调器
        Coordinator coordinator = new Coordinator(nodes, phaseCount);
        coordinator.start();
    }
    
    static class Coordinator {
        private final List<Node> nodes;
        private final int phaseCount;
        private final CyclicBarrier barrier;
        private final ExecutorService executor;
        
        public Coordinator(List<Node> nodes, int phaseCount) {
            this.nodes = nodes;
            this.phaseCount = phaseCount;
            this.barrier = new CyclicBarrier(nodes.size(), this::phaseCompleted);
            this.executor = Executors.newFixedThreadPool(nodes.size());
        }
        
        public void start() {
            System.out.println("开始分布式任务处理,共" + phaseCount + "个阶段");
            
            for (int phase = 1; phase <= phaseCount; phase++) {
                System.out.println("\n=== 第" + phase + "阶段开始 ===");
                
                // 提交所有节点的任务
                List<Future<String>> futures = new ArrayList<>();
                for (Node node : nodes) {
                    futures.add(executor.submit(() -> node.executePhase(phase, barrier)));
                }
                
                // 等待本阶段所有节点完成
                try {
                    for (Future<String> future : futures) {
                        future.get();
                    }
                } catch (Exception e) {
                    System.err.println("阶段" + phase + "执行异常: " + e.getMessage());
                    break;
                }
            }
            
            executor.shutdown();
            System.out.println("\n所有分布式任务处理完成!");
        }
        
        private void phaseCompleted() {
            System.out.println("═══════════════════════════════");
            System.out.println("当前阶段所有节点执行完成");
            System.out.println("═══════════════════════════════");
        }
    }
    
    static class Node {
        private final String name;
        
        public Node(String name) {
            this.name = name;
        }
        
        public String executePhase(int phase, CyclicBarrier barrier) {
            try {
                // 模拟节点处理时间
                int processTime = new Random().nextInt(3) + 1;
                System.out.println(name + " 开始阶段" + phase + "处理(预计" + processTime + "秒)");
                
                Thread.sleep(processTime * 1000L);
                System.out.println(name + " 阶段" + phase + "处理完成");
                
                // 等待其他节点
                barrier.await();
                
                return name + " 阶段" + phase + "完成";
            } catch (Exception e) {
                return name + " 阶段" + phase + "失败: " + e.getMessage();
            }
        }
    }
  }  

八、 最佳实践与注意事项

1. 正确使用模式

bash 复制代码
// 推荐:使用try-catch正确处理异常
public void safeAwait(CyclicBarrier barrier) {
    try {
        barrier.await();
    } catch (InterruptedException e) {
        // 线程被中断
        Thread.currentThread().interrupt();
        System.out.println("线程被中断,处理中断逻辑");
    } catch (BrokenBarrierException e) {
        // 屏障被破坏
        System.out.println("屏障被破坏,处理异常逻辑");
    } catch (TimeoutException e) {
        // 等待超时
        System.out.println("等待超时,处理超时逻辑");
    }
}

2. 避免的陷阱

bash 复制代码
// 陷阱1:屏障线程数不匹配
public class BarrierMismatchDemo {
    
    public static void main(String[] args) {
        // 创建需要3个线程的屏障
        CyclicBarrier barrier = new CyclicBarrier(3);
        
        // 但只启动2个线程 - 这会导致死锁!
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        for (int i = 0; i < 2; i++) {
            executor.execute(() -> {
                try {
                    System.out.println("线程等待...");
                    barrier.await();  // 这里会永远等待!
                    System.out.println("线程继续");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        // 解决方法:使用超时版本
        // barrier.await(10, TimeUnit.SECONDS);
    }
}

// 陷阱2:屏障动作中的异常
public class BarrierActionExceptionDemo {
    
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            throw new RuntimeException("屏障动作异常!");
        });
        
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        for (int i = 0; i < 3; i++) {
            executor.execute(() -> {
                try {
                    System.out.println("线程到达屏障");
                    barrier.await();
                    System.out.println("线程通过屏障");
                } catch (Exception e) {
                    System.out.println("捕获异常:" + e.getCause().getMessage());
                }
            });
        }
        
        executor.shutdown();
    }
}

3. 性能优化建议

bash 复制代码
// 对于大量线程的场景,考虑使用Phaser替代
import java.util.concurrent.Phaser;

public class PhaserAlternative {
    
    public static void main(String[] args) {
        int threadCount = 100;  // 大量线程
        
        // 使用Phaser替代CyclicBarrier(更灵活)
        Phaser phaser = new Phaser(threadCount);
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            executor.execute(() -> {
                try {
                    // 阶段1
                    System.out.println("线程执行阶段1");
                    phaser.arriveAndAwaitAdvance();  // 等待其他线程
                    
                    // 阶段2
                    System.out.println("线程执行阶段2");
                    phaser.arriveAndAwaitAdvance();
                    
                    // 完成
                    phaser.arriveAndDeregister();  // 注销
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
    }
}

九、 常见问题解答

Q1:CyclicBarrier和CountDownLatch到底有什么区别?
A:最核心的区别是使用次数和设计目的:

  • CountDownLatch是一次性的,用于一个/多个线程等待其他线程完成
  • CyclicBarrier是可循环的,用于多个线程相互等待,到达共同点后一起继续

Q2:如果await()时线程被中断会发生什么?
A: 如果线程在等待时被中断,会抛出InterruptedException

  • 屏障会被标记为"破坏"状态(broken)
  • 其他等待的线程会收到BrokenBarrierException

Q3:如何选择合适的parties数量?
A:

  • CPU密集型任务:选择与CPU核心数相等或略多的线程数
  • IO密集型任务:可以选择更多线程数(如CPU核心数×2)
  • 网络请求任务:可以根据网络延迟调整,通常更多线程能提高吞吐量

Q4:CyclicBarrier的屏障动作由哪个线程执行?

**A:**由最后一个到达屏障的线程执行屏障动作(barrierAction),执行完成后才唤醒其他线程。

Q5:什么时候应该使用reset()方法?
A:

  • 当屏障被破坏(broken)后,需要重新使用时
  • 需要提前结束当前"代",开始新的一轮时

注意:reset()会中断所有等待的线程,需谨慎使用

十、 总结

CyclicBarrier核心要点总结

特性 说明
循环使用 可重复使用,通过reset()重置
线程协作 多个线程相互等待,到达共同点
屏障动作 支持在所有线程到达后执行特定任务
超时支持 await()方法支持超时设置
状态监控 提供isBroken()、getNumberWaiting()等方法

适用场景

  • 多阶段任务处理:每个阶段需要所有线程完成才能进入下一阶段
  • 并行计算同步:多个计算任务完成后合并结果
  • 模拟测试:模拟并发用户同时操作
  • 分布式协调:多个服务节点需要同步状态

学习建议

  • 先理解概念:理解"屏障"和"循环"的含义
  • 动手实践:从简单的示例开始,逐步增加复杂度
  • 对比学习:与CountDownLatch、Semaphore、Phaser对比学习
  • 查看源码:理解dowait()方法的实现机制
  • 应用到实际:在实际项目中寻找适用场景

记住:CyclicBarrier是解决特定类型同步问题的工具,不是万能的。正确理解其适用场景,才能发挥最大价值。

相关推荐
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于uni—app的民宿预订系统为例,包含答辩的问题和答案
java·eclipse
bjxiaxueliang2 小时前
一文详解Cpp多线程编程:从传统pthread到现代thread实践指南
java·开发语言·jvm
机智的人猿泰山2 小时前
spring boot 运行测试类时:Error creating bean with name ‘serverEndpointExporter‘ 问题
java·spring boot·后端
爬山算法2 小时前
Hibernate(3)Hibernate的优点是什么?
java·后端·hibernate
秋邱2 小时前
Java面向对象进阶实战:用工厂模式+策略模式优化支付系统
java·bash·策略模式
heartbeat..3 小时前
网络通信核心知识全解析:模型、协议与 TCP 机制
java·网络·网络协议·tcp/ip
weixin_440730503 小时前
Java基础学习day01
java·开发语言·学习
天远云服3 小时前
Go 语言实战:构建高并发天远“全国自然人人脸比对 V3”微服务网关
java·大数据·微服务·golang
PPPPickup3 小时前
easychat项目复盘---管理端系统设置
java·开发语言·前端