在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();// 解锁
}
}
核心逻辑总结
- 线程到齐前:每个线程调用
await()→ 计数器减1 → 进入等待; - 所有线程到齐:计数器减为0 → 执行回调任务(可选)→ 重置栅栏(计数器恢复为初始值)→ 唤醒所有等待线程;
- 异常情况:线程中断、超时 → 破坏栅栏 → 唤醒所有线程 → 抛出异常。
四、 实战代码示例
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)
小美点菜完成
小明点菜完成
小丽点菜完成
小华点菜完成
结果解读
- 4个同学(线程)陆续到达餐厅(执行完"赶路"任务),各自调用
await()进入等待; - 最后一个同学(同学3)到达后,触发回调任务(呼叫服务员);
- 回调任务执行完,栅栏打开,所有等待的同学(线程)被唤醒,继续执行"点餐"逻辑;
- 返回值"到达顺序号":最后一个到达的线程返回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是解决特定类型同步问题的工具,不是万能的。正确理解其适用场景,才能发挥最大价值。