CyclicBarrier源码阅读
本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习
本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,否则你会不知道我在说什么
简介
说起CyclicBarrier不得不提一下CountDownLatch,他们最明显的区别就是:
- CountDownLatch:将线程分为执行者和等待者
- CyclicBarrier:执行者和等待者都是同一批线程
如何理解呢,CountDownLatch大家都知道,设置一批工作线程,各自执行完任务就调用CountDownLatch.countDown,表示自己已经执行完任务,该工作线程就可以继续干别的事情了。而CyclicBarrier相当于在CountDownLatch.countDown之后设置了一道屏障,自己完成了任务后,还得等别的工作线程也完成了之后才能继续干别的事情。
实际上CountDownLatch就能简单实现一个CyclicBarrier的核心功能:
// 这两句由同一个线程执行
countDownLatch.countDown();
countDownLatch.await();
就是这么简单,在countDown后加个await就行,相当于执行者和等待者都是同一个线程罢了。在我看来CyclicBarrier只是将两条语句合成了一句:barrier.await()
。
当然,CyclicBarrier还支持了其他的更加完善的机制:
- 复用:不同于CountDownLatch只能用一次,每次count减到0后就不能再加回来了。CyclicBarrier可以复用,支持多轮等待
- barrier action:创建CyclicBarrier时可以传入一个Runnable作为barrier action,在最后一个线程到达(await)时,由该线程执行该barrier action,执行完毕之后,所有线程才能被唤醒继续执行
- breakage:如果在最后一个线程到达之前,正在await的线程因为中断、超时等原因退出了(相当于await提前返回了),那么其他所有正在await的线程都将收到BrokenBarrierException异常。注意barrier action抛异常的话也会造成BrokenBarrierException。如果BrokenBarrierException一旦抛出,除非reset,否则该轮一直处于未完成状态,并且处于breakage状态无法使用。
注意barrier action在最后一个到达的线程await时才执行,执行完毕后所有线程的await才会返回。假如不需要“执行完毕后所有线程的await才会返回”,你只希望在最后一个到达的线程await后所有线程马上得到释放(并且希望action依然由最后一个到达的线程执行),可以这么做:
if (barrier.await() == 0) {
// action
}
await返回当前线程是第几个到达的线程,0代表最后一个到达(n-1代表第一个, n-2代表第2个...)。
最后,对于breakage发生后的reset,文档建议最好重新new一个CyclicBarrier,而不是reset。首先肯定只能由其中一个线程来reset,既然大家都收到了BrokenBarrierException,谁来reset合适?假如选出了负责reset的那个线程,在其reset之前,其他线程都不能await,这里又存在一个线程间同步问题需要解决。综上,发生breakage后,如果还想继续使用CyclicBarrier,重新new一个是比较好的选择。
代码分析
成员变量
public class CyclicBarrier {
// Generation代表一轮
private static class Generation {
Generation() {}
// 该轮是否broken,即breakage
boolean broken;
}
// 保护下面的变量
private final ReentrantLock lock = new ReentrantLock();
// 用于唤醒所有等待的线程
private final Condition trip = lock.newCondition();
// 总线程数
private final int parties;
// barrier action
private final Runnable barrierCommand;
// 当前轮
private Generation generation = new Generation();
// 还没到达的线程数
private int count;
}
方法
轮次正常或异常结束相关方法:
// 唤醒当前轮所有线程,开启下一轮
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
// 将当前轮设置为失败,唤醒当前轮所有线程
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
重置:
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
上面的方法比较简单,核心都在doWait方法,这个方法是await方法的实现:
// 以下几种情况会使得dowait返回
// 1. 最后一个线程到达,本轮正常结束,以下其他情况都是本轮broken并抛异常
// 2. 本轮已是broken
// 3. 超时
// 4. 中断
// 5. barrier action抛异常
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 加锁访问
lock.lock();
try {
final CyclicBarrier.Generation g = generation;
// 情况2
if (g.broken)
throw new BrokenBarrierException();
// 情况4
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 还没到达的线程数-1
int index = --count;
// 是否最后一个到达
if (index == 0) {
Runnable command = barrierCommand;
// 执行barrier action
if (command != null) {
try {
command.run();
} catch (Throwable ex) {
// 情况5
breakBarrier();
// 注意,当barrier action抛出异常,本线程抛出的是barrier action的异常
// 其他线程抛出的是BrokenBarrierException
throw ex;
}
}
// 执行完毕,本轮成功,开始下一轮
nextGeneration();
return 0;
}
// 不是最后一个到达的线程
for (;;) {
try {
// 在条件变量上等待
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 情况4
// 如果本轮未结束
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// 本轮已经结束(已经下一轮或者已经broken),那么这个中断应该留给下一轮用
Thread.currentThread().interrupt();
}
}
if (g.broken)
// 情况2
throw new BrokenBarrierException();
if (g != generation)
// 情况1,本轮成功
return index;
if (timed && nanos <= 0L) {
// 情况3
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 解锁,退出
lock.unlock();
}
}
“不是最后一个到达的线程”的情况下为什么要无限循环执行呢,考虑Condition.await唤醒只有以下几种可能:要么被signal(broken或者新一轮)、要么超时、要么中断,分支基本都覆盖到了,估计是为了兜底,因为说不定Condition.await可能会诈尸,无故唤醒...或是怕别的地方没设计好,可能会来了个不应该来的signal。
参考链接
「StackOverflow」Real Life Examples For CountDownLatch and CyclicBarrier