CyclicBarrier
是 Java 并发包(java.util.concurrent
)中一个经典的同步工具类,由 Doug Lea 设计,用于协调多个线程在某个“屏障点”等待彼此,直到所有线程都到达后再继续执行。其核心特性是可重复使用(Cyclic
),适用于需要多线程分阶段协作的场景。本文将从源码入手,结合设计模式分析其实现原理,并给出常见使用场景与代码示例。
一、核心设计:基于锁与条件变量的循环屏障
1. 类结构概览
CyclicBarrier
的核心逻辑依赖 ReentrantLock
和 Condition
实现线程的阻塞与唤醒,并通过内部类 Generation
管理“代”的状态,确保屏障可以循环使用。
public class CyclicBarrier {
private static class Generation {
boolean broken = false; // 屏障是否被破坏
}
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; // 剩余等待的线程数(初始为 parties)
}
2. 关键方法源码解析
(1) 构造函数:初始化屏障参数
CyclicBarrier
有两个构造函数,支持传入线程数 parties
和可选的屏障任务 barrierCommand
(所有线程到达后执行)。
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierCommand) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties; // 初始剩余等待线程数为 parties
this.barrierCommand = barrierCommand;
}
(2) await()
:核心等待逻辑
await()
方法是 CyclicBarrier
的核心,其内部调用 dowait()
实现具体逻辑。线程调用 await()
后会阻塞,直到所有 parties
线程都到达屏障,或屏障被破坏(如超时、中断)。
public int await() throws InterruptedException, BrokenBarrierException {
return dowait(false, 0L);
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException, TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock(); // 加锁保证原子性
try {
final Generation g = generation;
if (g.broken) throw new BrokenBarrierException(); // 屏障已破坏
if (Thread.interrupted()) { // 当前线程被中断
breakBarrier(); // 标记屏障破坏并唤醒所有线程
throw new InterruptedException();
}
int index = --count; // 剩余等待线程数减 1
if (index == 0) { // 所有线程已到达
try {
if (barrierCommand != null) barrierCommand.run(); // 执行屏障任务
nextGeneration(); // 重置屏障到下一代
return 0; // 最后到达的线程返回 0
} finally {
if (!ranAction) breakBarrier(); // 屏障任务执行失败时标记破坏
}
}
// 未到达的线程进入循环等待
for (;;) {
try {
if (!timed) trip.await(); // 无超时等待
else if (nanos > 0) 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 <= 0) { // 超时处理
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock(); // 释放锁
}
}
关键逻辑说明:
- 加锁保证原子性:通过
ReentrantLock
保证对count
和generation
的修改是原子的。 Generation
代机制:每次屏障触发(所有线程到达)或重置(reset()
)时,生成新的Generation
实例,避免旧代的线程干扰新代。- 条件变量等待:未到达的线程通过
trip.await()
阻塞,直到最后一个线程到达后调用trip.signalAll()
唤醒所有线程。 - 异常处理:若线程被中断或超时,调用
breakBarrier()
标记屏障破坏(generation.broken = true
),并唤醒所有等待线程,避免线程永久阻塞。
(3) reset()
:重置屏障
reset()
方法用于将屏障重置为初始状态,中断当前所有等待的线程并生成新的代。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // 破坏当前代,唤醒所有线程
nextGeneration(); // 生成新代,重置 count
} finally {
lock.unlock();
}
}
private void breakBarrier() {
generation.broken = true;
count = parties; // 重置剩余等待数
trip.signalAll(); // 唤醒所有等待线程
}
private void nextGeneration() {
trip.signalAll(); // 唤醒旧代线程(实际已处理)
count = parties; // 重置剩余等待数
generation = new Generation(); // 新代
}
二、设计模式分析
1. 状态模式(State Pattern)
CyclicBarrier
通过 Generation
类表示屏障的状态(如 broken
标记)。不同状态下,await()
方法的行为不同:
- 若
generation.broken
为true
(屏障被破坏),所有后续await()
调用会直接抛出BrokenBarrierException
。 - 若
generation
切换(进入新代),等待线程被唤醒并继续执行。
状态模式将状态相关的行为封装在 Generation
中,简化了屏障的循环复用逻辑。
2. 模板方法模式(Template Method Pattern)
CyclicBarrier
的核心逻辑(如加锁、条件等待、状态检查)封装在 dowait()
方法中,而具体的屏障任务(barrierCommand
)由用户传入。这种设计允许用户自定义屏障触发时的行为,符合“开闭原则”。
3. 代理模式(Proxy Pattern)
CyclicBarrier
的外部接口(如 await
、reset
)实际委托给内部的 ReentrantLock
和 Condition
实现。外部类隐藏了锁与条件变量的复杂操作,提供了更简洁的 API。
三、常见使用场景与代码示例
1. 场景 1:多线程分阶段任务协作(如并行计算后合并结果)
多个线程并行处理子任务,完成后等待所有线程到达屏障,最后由一个线程合并结果。
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelComputationExample {
private static final int N = 3; // 线程数
private static final int[] data = {1, 2, 3, 4, 5, 6}; // 原始数据
private static int[] results = new int[N]; // 子任务结果
public static void main(String[] args) {
// 定义屏障任务:合并子任务结果
Runnable mergeTask = () -> {
int sum = 0;
for (int res : results) sum += res;
System.out.println("合并结果:" + sum);
};
CyclicBarrier barrier = new CyclicBarrier(N, mergeTask);
ExecutorService executor = Executors.newFixedThreadPool(N);
for (int i = 0; i < N; i++) {
final int threadId = i;
executor.submit(() -> {
// 子任务:计算数据块的和
int start = threadId * (data.length / N);
int end = (threadId == N - 1) ? data.length : (threadId + 1) * (data.length / N);
int sum = 0;
for (int j = start; j < end; j++) sum += data[j];
results[threadId] = sum;
System.out.println("线程 " + threadId + " 完成计算,结果:" + sum);
try {
barrier.await(); // 等待其他线程到达屏障
} catch (Exception e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
输出示例:
线程 0 完成计算,结果:3
线程 1 完成计算,结果:7
线程 2 完成计算,结果:11
合并结果:21
2. 场景 2:多线程等待所有线程准备就绪(如游戏加载)
多个玩家线程加载资源,全部加载完成后启动游戏。
import java.util.concurrent.CyclicBarrier;
public class GameStartExample {
public static void main(String[] args) {
int playerCount = 3;
CyclicBarrier barrier = new CyclicBarrier(playerCount,
() -> System.out.println("所有玩家加载完成,游戏开始!"));
for (int i = 0; i < playerCount; i++) {
final int playerId = i;
new Thread(() -> {
try {
System.out.println("玩家 " + playerId + " 开始加载...");
Thread.sleep((long) (Math.random() * 2000)); // 模拟加载时间
System.out.println("玩家 " + playerId + " 加载完成");
barrier.await(); // 等待其他玩家
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
输出示例:
玩家 0 开始加载...
玩家 1 开始加载...
玩家 2 开始加载...
玩家 1 加载完成
玩家 0 加载完成
玩家 2 加载完成
所有玩家加载完成,游戏开始!
3. 场景 3:循环执行的多阶段任务(如定期数据处理)
多个线程分阶段处理数据,每个阶段结束后等待所有线程,循环执行。
import java.util.concurrent.CyclicBarrier;
public class CyclicTaskExample {
public static void main(String[] args) {
int phaseCount = 2; // 阶段数
int threadCount = 2; // 线程数
CyclicBarrier barrier = new CyclicBarrier(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
new Thread(() -> {
for (int phase = 0; phase < phaseCount; phase++) {
System.out.println("线程 " + threadId + " 执行阶段 " + phase);
try {
barrier.await(); // 阶段结束,等待其他线程
System.out.println("线程 " + threadId + " 进入阶段 " + (phase + 1));
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
输出示例:
线程 0 执行阶段 0
线程 1 执行阶段 0
线程 0 进入阶段 1
线程 1 进入阶段 1
线程 0 执行阶段 1
线程 1 执行阶段 1
线程 0 进入阶段 2
线程 1 进入阶段 2
四、总结
CyclicBarrier
是基于 ReentrantLock
和 Condition
实现的循环屏障工具,通过 Generation
机制支持重复使用,适用于多线程分阶段协作场景。其核心设计模式包括状态模式(管理屏障状态)、模板方法模式(自定义屏障任务)和代理模式(隐藏底层同步细节)。理解其源码有助于更高效地使用该工具,并掌握 Java 并发包的设计思想。