CyclicBarrier 解析
1. 属性分析
//
private final ReentrantLock lock = new ReentrantLock();
// 等待触发的集合
private final Condition trip = lock.newCondition();
// Barrier数量
private final int parties;
// 触发之后的回调(也就是触发之后的动作)
private final Runnable barrierCommand;
// 表示一代线程
private Generation generation = new Generation();
// 还在等待的线程数量,一开始的时候,count等于parties,之后,只有有一个线程await,这个count就是--。
private int count;
2. 构造函数
构造函数没有什么说的,标准的赋值操作,需要注意,这里的parties
保存的是最原始的值,count
是会活动的。也就是说,每次只要有一个线程await了。那么,(一开始count等于parties)count就是–。count变为0的时候就会将count重新置为parties的值。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties; // 数量,这就是等待的数量
this.count = parties; // 同样的,把等待的数量赋值给parties。
this.barrierCommand = barrierAction; // 回调的command
}
3. 常用方法
1. await方法分析
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
dowait方法
//参数说明:
// timed:是否要超时等待
// nanos:等待时间
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// 这不是基于aqs来实现的。而是通过ReentrantLock来实现的。
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 拿到这一代线程的表示对象,用这个对象来表示满足count的线程集合。表示一代。
final Generation g = generation;
// 如果这一代需要中断。直接抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果当前线程中断了,就调用breakBarrier。做清楚操作,并且抛出异常。并且唤醒之前等待的线程,表示这一代的线程都不行了// 唤醒的线程在await方法之后,会有一个判断,如果为true,直接抛出异常。本线程抛出InterruptedException
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 每次都是 --count
int index = --count;
if (index == 0) { // 如果count等于0,就要触发了
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null) // 运行command
command.run();
// 标识位,标识运行成功
ranAction = true;
// 生成一个新的Generation,并且唤醒所有调用了await的线程。。重新设置count为parties的值(也就是一开始的值).被唤醒的线程就会走下面的 if (g != generation) 的判断逻辑,因为引用关系不一样的 。说明上一代已经执行过了,唤醒的就赶紧返回把。
nextGeneration();
return 0;
} finally {
// 如果运行没有成功,
if (!ranAction)
//那只有一种情况了,在运行的时候发送了异常。发生了异常,这种情况在下面breakBarrier里面已经说了
breakBarrier();
}
}
// 如果--之后,count没有变为0.就循环开始等待了
for (;;) {
try {
// 如果不要超时等待,需要一直等待的话,就一直等着
if (!timed)
trip.await();
// 等待超时时间
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 如果等待的时候被中断掉。
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// 到这里只有两种情况
// 1. 换代更新了。
// 2. broker正常
// 这两种情况都说明,任务已经快结束了,或者已经结束了,但是这个时候却发生了中断操作。只能中断当前操作了,这里需要返回结果吗?不需要。因为中断会打破等待操作。况且,这个返回结果没有意义
Thread.currentThread().interrupt();
}
}
// 到这里就说明当前等待的线程被唤醒了。
// 如果唤醒之后generation的broken。需要中断。直接抛出异常。
if (g.broken)
throw new BrokenBarrierException();
// 如果代不一样了,也就是说,上一代的command已经完成了(只有在command执行结束之后,才会更新换代,或者reset方法)
// 那这肯定是属于上一代的线程,当前线程直接返回就好了。
if (g != generation)
return index;
// 如果需要超时等待,并且nanos小于等于0,就报错。nanos在上面的await里面,指的是剩下的等待时间。
// 这个是一个估算的。
if (timed && nanos <= 0L) {
// 清楚操作,到这里就说明超时了。
// 这段代码是在for循环里面的。只有超时等待,会走到这里,如果第一次被唤醒。
// 还剩下时间。走到这里的判断。如果nanos小于等于0,就说明超时了。
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
breakBarrier方法
在异常情况下,做清楚操作。
会在下面的几个地方调用
-
一开始,当前线程没有等待前,判断线程中断的时候。
表示当前线程被中断了,就会导致这一批线程的等待出现问题。所以,将generation的broken设置为true。
但是要注意,这个方法里面没有创建新generation的操作,只是改了标志位,同一批的线程获取到锁,会在判断中断前面检查generation的broken,如果是true,直接抛出异常。
总结下来就是:
如果一批线程里面,还没有等待之前,被中断了。这一批线程的await操作都会发生异常。必须重新调用reset方法,复位。否则后序的等待都会失败
-
command运行失败的时候。
如果传递进来的command运行报错。就会调用。和上面是一样的结果。
没有直接修改generation。所以,下一批的线程还是会报错,因为这里还只是修改了generation。并没有创建。
之前park住的线程,还有没有等待的线程,都会检测到。从而报错,直到调用了reset方法。
-
等待的时候,被打断的时候。如果
g == generation && ! g.broken
判断引用关系,如果引用关系相等,说明还是同一批线程,如果broken为true。最起码说明不是自己线程出现了问题,肯定是同一批次的别的线程出现了问题,也不可能是command发生了问题。还是同样的处理逻辑。并且利用异常的方法跳出循环。
后序唤醒的线程。还是会走上面的逻辑,判断broken。抛出异常。
-
等待超时
等待超时就直接抛出异常。唤醒的线程也像上面一样,还是会继续抛出异常。
-
调用reset的时候
看这里的逻辑很清晰
将generation的broken变为true。重新设置parties。唤醒所有等待的线程。
问题?
generation是什么?有什么意义?
generation表示一代线程。也就是在没有触发之前,等待的线程,用generation来表示这一代线程。 里面的broken里面表示是否中断过。
generation既然表示 一代线程的话。既然是标志位,用true,false不行吗?
不行。要知道。 trip.signalAll();的唤醒也是只能唤醒一个线程,并且,获取锁的时候线程是有随机性的。如果有布尔值。就可能会导致上一次的generation会影响下一次的generation。所以,可以看到在dowait方法里面会用引用判断。
private void breakBarrier() {
// 将generation的broken变为true
generation.broken = true;
// 重新设置count
count = parties;
// 唤醒所有的等待的线程。
trip.signalAll();
}
Generation类
就一个简单的静态内部类。
private static class Generation {
boolean broken = false;
}
2. isBroken
返回generation里面的broker的属性值,generation代表这一代的线程。broker代表的意思是这一代的线程是否被破坏。
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
3. reset
重新设置CyclicBarrier
调用的方法在上面和下面都有分析。具体看他们各自方法的内容。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//
breakBarrier();
//
nextGeneration();
} finally {
lock.unlock();
}
}
4. nextGeneration
生成新的Generation,重新设置parties,唤醒所有等待的线程。
private void nextGeneration() {
// 唤醒所有的等待的线程
trip.signalAll();
// 重新设置count为parties
count = parties;
// 重新创建Generation。表示下一代的需要等待的线程。
generation = new Generation();
}
5. getNumberWaiting
返回已经等待的线程的数量
count表示还需要等待多少。
parties表示初始值
parties - count 表示已经等待的线程的数量。
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
我感觉,对于这个理解的难点在于Generation
。要知道线程获取锁和等待都是随机的。将Generation和线程的随机性联系在一起。并且结合代(一代线程,CyclicBarrier本事就是当指定数量的线程到达之后,command才可以触发。并且还能重复使用),
。就比较好理解了
关于 CyclicBarrier就分析到这里了,如有不正确的地方,欢迎指出。谢谢。