首先准备一个可以跑的Demo
@Slf4j
public class CyclicBarrierExample{
//1、实例化一个CycleBarrier
private static CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
//2、创建线程池
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
//4、关闭线程池
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
//3、堵塞等待任务达到给定参数的数量后释放
barrier.await();
log.info("{} continue", threadNum);
}
}
先说一个不是非常恰当的比喻来说明下我对CyclicBarrier的理解
有一根管子里面有一个可以进行自我修复的薄膜,薄膜能承受最大的重量为parties个单位,然后我们向管子里面扔石子,一个石子的重量为1一个单位,当石子遇到薄膜的时候被堵在薄膜上不能够继续往下下落,直到石子的数量大于parties个单位,于是可以冲破薄膜往下继续下落,薄膜在等所有石子通过之后立即自我修复然后等待下一批石子。
我们通过CyclicBarrierExample去简单猜测一下源码:创建的时候我们给定了一个参数parties=5,在执行线程任务的时候每一个任务执行之前内部有一个计数器会加1,然后该线程被堵塞,直到内部的计数器等于我们传递的参数parties的时候,被堵塞的任务被执行。
下面验证下我们的猜测是否正确
我们先看看CyclicBarrier的构造函数
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
这里可以看到初始化CyclicBarrier的时候一共填充了内部的三个字段:
1、parties:这里就是我们刚刚讨论的parties
2、count:这里初始化的值等于我们的parties,因此每次传入一个线程是执行减1操作而非我们刚刚所说的加1,不过原理是一样的。
3、barrierCommand:这里作为一个可选的参数,就是在屏障被打破的时候,也就是线程执行任务之前执行的操作。
我们继续看看序号3的await方法做了什么
public int await() throws InterruptedException, BrokenBarrierException {
try {
//第二个参数是超时时间,传入空参的时候表示不设置超时时间
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
继续看dowait,是CyclicBarrier的核心方法
/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//堵塞
lock.lock();
try {
//得到当前线程的批次(代)信息
final Generation g = generation;
//如果当前批次已经执行过了
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//计数器(还记得刚刚解释的count吗)减1
int index = --count;
//如果减1之后为0,说明线程已经累计到一定的数量,可以冲破屏障了
if (index == 0) {
//是否可以执行线程任务
boolean ranAction = false;
try {
//冲破屏障后需要执行的操作,入参,上面解释过,我们Demo传入的是null
final Runnable command = barrierCommand;
if (command != null)
command.run();
//标记可以执行线程任务了
ranAction = true;
//直接进入到下一代,重新初始化参数
nextGeneration();
return 0;
} finally {
if (!ranAction)
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 {
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//无论是否发生了异常,先把锁释放掉
lock.unlock();
}
}
这里源码逻辑还是很清楚的,没有什么特别难的地方。
在读源码的时候发现一个细节,就是CyclicBarrier不像CountDownLatch一样定义了一个Sync继承于AbstractQueuedSynchronizer类,那CyclicBarrier为什么也号称是基于AQS的呢?ReentrantLock里面有一个公平锁FairSync和非公平锁UnFairSync继承于Sync,可以发现其声明了一个ReentrantLock字段,而CyclicBarrier实现的是空参的ReentrantLock默认是非公平锁。所以CyclicBarrier也可以说是基于AQS的。