CyclicBarrier源码分析
文章目录
零、前言
在阅读本篇文章前,建议先学习下面两篇文章:
一、CyclicBarrier 与 CountdownLatch 的区别
-
在CountDownLatch中,执行countDown方法的线程和执行await方法的线程不是一类线程。执行countDown的线程不会被挂起,调用await方法的线程会阻塞等待共享锁。
-
在CyclicBarrier中,将count值递减的线程和执行await方法的线程是一类线程,它们在执行完递减count的操作后,如果count不为0,可能同时被挂起。
-
CountDownLatch是一次性的,当count被减为0后,不会被重置。而CyclicBarrier在线程通过栅栏后,会开启新一轮操作,count会被重置,即可重复执行多次任务。
-
CountDownLatch使用的是共享锁,count不为0时,线程在 CLH 中等待。
-
CyclicBarrier是基于独占锁ReentrantLock和条件队列实现的,count不为0时,线程进入条件队列中等待,当count降为0后,将被signalAll()方法唤醒到 CLH 中去争锁。
二、概述
- 属性
private static class Generation {
boolean broken = false;
}
// CyclicBarrier 的基础实现
// 独占锁
private final ReentrantLock lock = new ReentrantLock();
// 条件队列
private final Condition trip = lock.newCondition();
// “代”:即每开启一轮新的 barrier
private Generation generation = new Generation();
// 代表一个任务,类似于钩子方法。可以为 null
// 所有线程到齐后,在一同通过 barrier 之前,就会执行这个对象的 run 方法
private final Runnable barrierCommand;
// 表征线程的数量
// parties 为参与线程的总数,即需要同时通过 barrier 的线程数
// final 类型,由构造函数初始化
private final int parties;
// count 为还需等待的线程数,初始值为 parties
private int count;
三、常用方法
3.1 nextGeneration()——开启新的一代
- 该方法用于开启新的“一代”,通常是被最后一个调用await方法的线程调用。
- 该方法的主要工作就是唤醒当前这一代中所有等待在条件队列里的线程,同时将count恢复为parties,开启新的一代。
private void nextGeneration() {
// 唤醒当前这一代中所有等待在条件队列里的线程
trip.signalAll();
// 恢复 count,开启新的一代
count = parties;
generation = new Generation();
}
3.2 breakBarrier()
- 打破现有的栅栏,让所有线程通过。
- 即可能线程数没能达到count的要求,此时强行打破barrier,执行后续任务。
private void breakBarrier() {
// 标记 broken 状态
generation.broken = true;
// 恢复 count
count = parties;
// 唤醒当前这一代中所有等待在条件队列里的线程(因为栅栏已经打破了)
trip.signalAll();
}
3.3 reset()
- 用于将barrier恢复成初始的状态,它的内部就是简单地调用了breakBarrier方法和nextGeneration方法。
- 该方法执行前需要先获得锁。
- 执行该方法时有线程正等待在barrier上,它将立即返回并抛出BrokenBarrierException。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// break the current generation
breakBarrier();
// start a new generation
nextGeneration();
} finally {
lock.unlock();
}
}
3.4 await() 与 await(long timeout, TimeUnit unit)
await()是集“countDown”和“阻塞等待”于一体的方法。
- await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 最终是调用 dowait()
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
- await(long timeout, TimeUnit unit)
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException {
// 最终是调用 dowait()
return dowait(true, unit.toNanos(timeout));
}
3.4.1 dowait()
- 执行 await 方法的线程必须先获取锁。
- 如果正在 await 时发现 barrier 已经被 break 了,则直接抛出 BrokenBarrierException。
- 如果当前线程被中断了,则先将栅栏打破,再抛出InterruptedException。
- 如果等待的线程数为 0 了,说明所有的 parties 都到齐了,可以直接唤醒所有等待的线程,让大家一起通过栅栏,同时重置栅栏。唤醒之前如果传入了 barrierCommand,则还要执行相应任务。
- 如果 count 不为 0,就将当前线程挂起,直到所有的线程到齐后被唤醒,或者超时,或者中断发生。
// timed:是否设置超时机制
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
// 所有执行 await 方法的线程必须是已经持有了锁的,所以这里必须先获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
// 调用 breakBarrier 会将当前“代”的 broken 属性设为 true
// 如果一个正在 await 的线程发现 barrier 已经被 break 了,则直接抛出 BrokenBarrierException
if (g.broken)
throw new BrokenBarrierException();
// 如果当前线程被中断了,则先将栅栏打破,再抛出 InterruptedException
// 这么做的原因是,所以等待在 barrier 的线程都是相互等待的,如果其中一个被中断了,那其它的就不用等了
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 当前线程已经来到了栅栏前,先将等待的线程数减一
int index = --count;
// 如果等待的线程数为 0 了,说明所有的 parties 都到齐了
// 则可以唤醒所有等待的线程,让大家一起通过栅栏,并重置栅栏
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
// 如果创建 CyclicBarrier 时传入了 barrierCommand
// 通过栅栏前有一些额外的工作要做
command.run();
ranAction = true;
// 唤醒所有线程,开启新一代
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果 count 不为 0,就将当前线程挂起,直到所有的线程到齐,或者超时,或者中断发生
for (;;) {
try {
// 如果没有设定超时机制,则直接调用 condition 的 await 方法
if (!timed)
// 当前线程在这里被挂起,并加入条件队列
trip.await();
else if (nanos > 0L)
// 如果设了超时,则等待指定的时间
// 当前线程在这里被挂起,超时时间到了就会自动唤醒
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 执行到这里说明线程被中断了
// 如果线程被中断时还处于当前这一“代”,并且当前这一代还没有被 broken,则先打破栅栏
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// 注意来到这里有两种情况
// 一种是 g!=generation,说明新的一代已经产生了,所以没有必要处理这个中断,只要再自我中断一下就好
// 一种是 g.broken = true, 说明中断前栅栏已经被打破了,既然中断发生时栅栏已经被打破了,也没有必要再处理这个中断了
Thread.currentThread().interrupt();
}
}
------------------------------------------------------------------------
// 注意,执行到这里是对应于线程从 await 状态被唤醒了
// 先检测 broken 状态,能使 broken 状态变为 true 的,只有 breakBarrier 方法,到这里对应的场景是:
// 1. 其它执行 await 方法的线程在挂起前就被中断了
// 2. 其它执行 await 方法的线程在还处于等待中时被中断了
// 3. 最后一个到达的线程在执行 barrierCommand 的时候发生了错误
// 4. reset() 方法被调用
if (g.broken)
throw new BrokenBarrierException();
// 如果线程被唤醒时,新一代已经被开启了,说明一切正常,直接返回
if (g != generation)
return index;
// 如果是因为超时时间到了被唤醒,则打破栅栏,返回 TimeoutException
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}