[Java并发]CyclicBarrier
CyclicBarrier 循环栅栏
CyclicBarrier 字面意思回环栅栏(循环屏障),通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
CyclicBarrier的使用
构造方法
// parties表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
public CyclicBarrier(int parties)
// 用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景(该线程的执行时机是在到达屏障之后再执行)
重要方法
//屏障 指定数量的线程全部调用await()方法时,这些线程不再阻塞
// BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
//循环 通过reset()方法可以进行重置
CyclicBarrier有两个构造函数
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
第一个参数,其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier已经到达屏障位置,线程被阻塞。
第二个参数,表示线程都处于barrier时,一起执行之前,其中barrierAction任务会在所有线程到达屏障后执行。
让线程处于barrier状态的方法await()
public int await()
public int await(long timeout, TimeUnit unit)
第一个默认方法,表示要等到所有的线程都处于barrier状态,才一起执行。
第二个方法,指定了等待的时间,当所有线程没有都处于barrier状态,又到了指定的时间,所在的线程就继续执行了。
其它的一些方法
//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()
//用于查询阻塞等待的线程是否被中断
boolean isBroken()
CyclicBarrier是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
public CyclicBarrier(int parties, Runnable barrierAction)方法解释
CyclicBarrier
是 Java 中的一个同步辅助类,它允许一组线程互相等待,直到它们都达到一个共同的屏障点。这个类的一个常见用途是实现多线程并行任务的协调。
构造方法 public CyclicBarrier(int parties, Runnable barrierAction)
创建了一个 CyclicBarrier
实例,参数如下:
-
parties
:这是一个整数,表示参与同步的线程数量。当这些线程中的每一个都调用了await()
方法,屏障就会被解除,所有被阻塞的线程将继续执行。 -
barrierAction
:这是一个Runnable
对象,表示当最后一个线程到达屏障时执行的操作。这是在所有线程都调用了await()
方法并且屏障被解除后,由最后一个调用await()
方法的线程执行的动作。可以用来执行一些在所有线程都到达屏障点之后需要进行的工作,例如合并结果、更新状态等。
使用示例
以下是一个简单的使用示例,展示了如何使用 CyclicBarrier
来同步一组线程,并在所有线程都到达屏障后执行一个动作:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
final int parties = 3;
Runnable barrierAction = () -> System.out.println(Thread.currentThread().getName() + "All parties have arrived at the barrier, let's play!");
CyclicBarrier barrier = new CyclicBarrier(parties, barrierAction);
for (int i = 0; i < parties; i++) {
new Thread(new Task(barrier)).start();
}
}
}
class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
barrier.await();
System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Thread-0 is waiting at the barrier.
Thread-1 is waiting at the barrier.
Thread-2 is waiting at the barrier.
Thread-2All parties have arrived at the barrier, let's play!
Thread-2 has crossed the barrier.
Thread-1 has crossed the barrier.
Thread-0 has crossed the barrier.
详细解释
-
初始化
CyclicBarrier
:parties
设置为 3,意味着有三个线程需要同步。barrierAction
是一个简单的输出语句,当所有线程到达屏障时执行。
-
启动线程:
- 循环创建并启动三个线程,每个线程都运行
Task
对象。
- 循环创建并启动三个线程,每个线程都运行
-
线程执行
await()
方法:- 每个线程调用
await()
方法并等待其他线程到达屏障。 - 当第三个线程调用
await()
方法时,屏障被解除,所有线程继续执行。 barrierAction
由最后一个到达屏障的线程执行。
- 每个线程调用
注意事项
-
屏障重用:
CyclicBarrier
可以重用,即当所有线程都通过屏障后,可以重新使用同一个CyclicBarrier
实例来同步下一组线程。
-
异常处理:
- 在调用
await()
方法时,可能会抛出BrokenBarrierException
或InterruptedException
,需要在代码中进行处理。
- 在调用
通过 CyclicBarrier
,我们可以轻松地实现多线程之间的协调和同步,确保所有线程都在特定的同步点汇合并执行相应的操作。
CyclicBarrier 应用场景
利用 CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
利用 CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景
模拟合并计算场景
利用 CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
import java.util.Set;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
//保存每个学生的平均成绩
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CyclicBarrier cb = new CyclicBarrier(3, () -> {
int result = 0;
Set<String> set = map.keySet();
for (String s : set) {
result += map.get(s);
}
System.out.println(Thread.currentThread().getName() + "同学负责计算三人平均成绩 :" + (result / 3) + "分");
});
for (int i = 0; i < 3; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
//获取学生平均成绩
int score = (int) (Math.random() * 40 + 60);
map.put(Thread.currentThread().getName(), score);
System.out.println(Thread.currentThread().getName()
+ "同学的平均成绩为:" + score);
try {
//执行完运行await(),等待所有学生平均成绩都计算完毕
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
}
pool-1-thread-1同学的平均成绩为:82
pool-1-thread-3同学的平均成绩为:96
pool-1-thread-2同学的平均成绩为:60
pool-1-thread-2同学负责计算三人平均成绩 :79分
模拟“人满发车”的场景
利用CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, 5, 1000, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
(r) -> new Thread(r, counter.addAndGet(1) + " 号 "),
new ThreadPoolExecutor.AbortPolicy());
CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
() -> {
System.out.println(Thread.currentThread().getName() + " 充当裁判,发令");
});
for (int i = 0; i < 15; i++) {
threadPoolExecutor.submit(new Runner(cyclicBarrier));
}
}
static class Runner extends Thread {
private CyclicBarrier cyclicBarrier;
public Runner(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 选手已就位");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " 开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
输出结果:
2 号 选手已就位
1 号 选手已就位
3 号 选手已就位
4 号 选手已就位
5 号 选手已就位
5 号 充当裁判,发令
5 号 开始执行
2 号 开始执行
4 号 开始执行
1 号 开始执行
3 号 开始执行
3 号 选手已就位
4 号 选手已就位
5 号 选手已就位
1 号 选手已就位
2 号 选手已就位
2 号 充当裁判,发令
2 号 开始执行
3 号 开始执行
4 号 开始执行
5 号 开始执行
1 号 开始执行
2 号 选手已就位
1 号 选手已就位
5 号 选手已就位
4 号 选手已就位
3 号 选手已就位
3 号 充当裁判,发令
3 号 开始执行
1 号 开始执行
5 号 开始执行
4 号 开始执行
2 号 开始执行
CyclicBarrier 源码分析
CyclicBarrier 流程
主要是的流程:
获取锁 如果 count != 0 就进入阻塞;
进入阻塞之前,首先需要进入条件队列,然后释放锁,最后阻塞;
如果 count != 0 会进行一个唤醒,将所有的条件队列中的节点转换为阻塞队列;
被唤醒过后会进行锁的获取,如果锁获取失败,会进入 lock 的阻塞队列;
如果锁获取成功,进行锁的释放,以及唤醒,同步队列中的线程。
下面是一个简单的流程图:
下面是具体的一些代码调用的流程。
几个常见的问题?
-
一组线程在触发屏障之前互相等待,最后一个线程到达屏障后唤醒逻辑是如何实现的. 唤醒的过程是通过调用 java.util.concurrent.locks.Condition#signalAll唤醒条件队列上的所有节点。
-
删栏循环使用是如何实现的? 实际上一个互斥锁 ReentrantLock 的条件队列和阻塞队列的转换。
-
条件队列到同步队列的转换实现逻辑 ? 转换过程中,首先会先将条件队列中所有的阻塞线程唤醒,然后会去获取 lock 如果获取失败,就进入同步队列。
CyclicBarrier 与 CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置(可以不显示调用reset,默认也会调用reset)。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
CyclicBarrier还提供getNumberWaiting(可以获得CyclicBarrier阻塞的线程数量)、isBroken(用来知道阻塞的线程是否被中断)等方法。
CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同。CountDownLatch一般用于一个或多个线程,等待其他线程执行完任务后,再执行。CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
CyclicBarrier 还可以提供一个 barrierAction,合并多线程计算结果。
CyclicBarrier是通过ReentrantLock的"独占锁"和Conditon来实现一组线程的阻塞唤醒的,而CountDownLatch则是通过AQS的“共享锁”实现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南