CyclicBarrier源码分析
CyclicBarrier的简介
CyclicBarrier的字面意思是可循环使用的屏障。他要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。
CyclicBarrier和CountDownLatch的区别
①CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法进行重置,所以CyclicBarrier可以处理更为复杂的场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。
②CyclicBarrier还提供了其他有用的方法,比如getNumberWaiting()方法可以获取CyclicBarrier阻塞的线程数量。isBroken()方法可以了解阻塞的线程是否被中断。
CyclicBarrier的源码分析
1、CyclicBarrier的构造方法及其重要的属性
private static class Generation {
//表示当前“代”是否被打破,如果代被打破 ,那么再来到这一代的线程 就会直接抛出 BrokenException异常
//且在这一代 挂起的线程 都会被唤醒,然后抛出 BrokerException异常。
boolean broken = false;
}
//因为barrier实现是依赖于Condition条件队列的,condition条件队列必须依赖lock才能使用。
private final ReentrantLock lock = new ReentrantLock();
//线程挂起实现使用的 condition 队列。条件:当前代所有线程到位,这个条件队列内的线程 才会被唤醒。
private final Condition trip = lock.newCondition();
//Barrier需要参与进来的线程数量
private final int parties;
//当前代 最后一个到位的线程 需要执行的事件
private final Runnable barrierCommand;
//表示barrier对象 当前 “代”
private Generation generation = new Generation();
//表示当前“代”还有多少个线程 未到位。
//初始值为parties
private int count;
public CyclicBarrier(int parties, Runnable barrierAction) {
//拦截的线程数目如果小于0,直接抛出异常
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
//count的初始值 就是parties,后面当前代每到位一个线程,count--
this.count = parties;
//当前“代”最后一个到位的线程,需要执行的事件(可以为null)
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
2、CyclicBarrier的await()方法(超时的wait逻辑相似,这边只分析不超时的await())
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
/**
* Main barrier code, covering the various policies.
* timed:表示当前调用await方法的线程是否指定了 超时时长,如果true 表示 线程是响应超时的
* nanos:线程等待超时时长 纳秒,如果timed == false ,nanos == 0
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//加锁
//为什么要加锁呢?
//因为 barrier的挂起 和 唤醒 依赖的组件是 condition。
lock.lock();
try {
//获取barrier当前的 “代”
final Generation g = generation;
//如果当前代是已经被打破状态,则当前调用await方法的线程,直接抛出Broken异常
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程的中断标记位 为 true,则打破当前代,然后当前线程抛出 中断异常
if (Thread.interrupted()) {
//1.设置当前代的状态为broken状态 2.唤醒在trip 条件队列内的线程
breakBarrier();
throw new InterruptedException();
}
//执行到这里,说明 当前线程中断状态是正常的 false, 当前代的broken为 false(未打破状态)
//正常逻辑...
//假设 parties 给的是 5,那么index对应的值为 4,3,2,1,0
int index = --count;
if (index == 0) { // tripped
//标记:true表示 最后一个线程 执行cmd时未抛异常。 false,表示最后一个线程执行cmd时抛出异常了.
//cmd就是创建 barrier对象时 指定的第二个 Runnable接口实现,这个可以为null
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
//command.run()未抛出异常的话,那么线程会执行到这里。
ranAction = true;
//开启新的一代
//1.唤醒trip条件队列内挂起的线程,被唤醒的线程 会依次 获取到lock,然后依次退出await方法。
//2.重置count 为 parties
//3.创建一个新的generation对象,表示新的一代
nextGeneration();
return 0;
} finally {
//如果command.run()执行抛出异常的话,会进入到这里。
if (!ranAction)
breakBarrier();
}
}
//执行到这里,说明当前线程 并不是最后一个到达Barrier的线程..此时需要进入一个自旋中.
//自旋,一直到 条件满足、当前代被打破、线程被中断 或等待超时
for (;;) {
try {
//条件成立:说明当前线程是不指定超时时间的
if (!timed)
//当前线程 会 释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
trip.await();
else if (nanos > 0L)
//说明当前线程调用await方法时 是指定了 超时时间的!
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//抛出中断异常,会进来这里。
//什么时候会抛出InterruptedException异常呢?
//Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
//条件一:g == generation 成立,说明当前代并没有变化。
//条件二:! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
//执行到else有几种情况?
//1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
//2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出 brokenBarrier异常。也记录下中断标记位。
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
//唤醒后,执行到这里,有几种情况?
//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
//2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
//3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (g.broken)
throw new BrokenBarrierException();
//唤醒后,执行到这里,有几种情况?
//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
//3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (g != generation)
return index;
//唤醒后,执行到这里,有几种情况?
//3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
/**
* 开启下一代,当这一代 所有线程到位后(假设barrierCommand不为空,还需要最后一个线程执行完事件),会调用nextGeneration()开启新的一代。
*/
private void nextGeneration() {
//将在trip条件队列内挂起的线程 全部唤醒
// signal completion of last generation
trip.signalAll();
//重置count为parties
// set up next generation
count = parties;
generation = new Generation();
}
/**
* Sets current barrier generation as broken and wakes up everyone.
* Called only while holding lock.
* 打破barrier屏障,在屏障内的线程 都会抛出异常..
*/
private void breakBarrier() {
//将代中的broken设置为true,表示这一代是被打破了的,再来到这一代的线程,直接抛出异常.
generation.broken = true;
count = parties;
//将在trip条件队列内挂起的线程 全部唤醒,唤醒后的线程 会检查当前代 是否是打破的,
//如果是打破的话,接下来的逻辑和 开启下一代 唤醒的逻辑不一样.
trip.signalAll();
}