目录
上篇文章介绍了CountDownLatch,这篇文章重点介绍CyclicBarrier。上篇文章里说过,CountDownLatch是基于AQS的共享模式实现的,而CyclicBarrier其实是基于ReentrantLock和Condition来实现的。从名称上来看CyclicBarrier就是可循环的栅栏,如果我们把CountDownLatch当成一个计数器,那CyclicBarrier就是可重复利用的计数器,它拥有重置的功能,而CountDownLatch减到0就没了。
下面开始分析CyclicBarrier的源码,先开看一下CyclicBarrier类中都有哪些重要属性
public class CyclicBarrier {
/**
* 这里把每次跳过栅栏用一个Generation的实例来表示,
* 每次穿过栅栏Generation就会发生变化或者重置
*/
private static class Generation {
boolean broken = false;
}
/** 栅栏入口的锁*/
private final ReentrantLock lock = new ReentrantLock();
/** Condition就是等待某个条件,这里是代表:CyclicBarrier要想穿过栅栏
* 需要等待所有线程都到达了栅栏
*/
private final Condition trip = lock.newCondition();
/** 其实就是线程数 */
private final int parties;
/* 这个就是穿过栅栏前,需要执行的操作 */
private final Runnable barrierCommand;
/** 当前的Generation,注:这时候它的broken字段还是false */
private Generation generation = new Generation();
/**
* count代表着还在等待,也就是未到达栅栏的线程
* count的初始值为构造方法传入的parties,
* 然后回递减,一直减到0
*/
private int count;
............
}
await
CyclicBarrier最核心的就是await()方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 这里传入false代表不带有超时机制
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
// CyclicBarrier核心代码
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 在操作之前,必先获取到栅栏的独占锁
lock.lock();
try {
// 获取代表当前这一代的Generation实例
final Generation g = generation;
// 如果栅栏已经被打破,抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 判断线程中断状态。响应中断
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 我们说了count代表还没到达栅栏的线程,这里对count减1
// 代表当前线程已经到达了栅栏。index是count递减后得到的值
// 这个方法最终返回的也是这个index
int index = --count;
// 如果index==0,说明所有的线程都到达了栅栏
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
// 执行穿越栅栏前的操作
command.run();
ranAction = true;
// 这个方法很关键,会唤醒所有在栅栏上等待的线程,
// 更新Generation对象,重新开启新的一代
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果当前线程是最后一个到达栅栏的,那么在上面if(index==0)这个分支里就返回了,
// 如果当前线程到达栅栏的时候还有别的线程没有到达栅栏。就会执行这个for循环。
for (;;) {
try {
// 如果不支持超时机制调用condition的await()方法,
// 将当前线程加入到condition队列,然后挂起,
// 等着进入AQS等待(同步)队列,直到最后到达栅栏的那个线程,
// 执行condition的signalAll()方法唤醒所有在栅栏上等待的线程
if (!timed)
trip.await();
else if (nanos > 0L)
// 如果支持超时机制。那么调用condition带有超时机制的await方法
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 如果条件成立,说明线程执行condition.await发生了中断,
// 那么,打破栅栏,抛出异常
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// 否则说明generation对象发生了变化
// 即最后一个到达栅栏的线程已经完成了await()
// 那么这个时候没必要再抛出interrupted异常
Thread.currentThread().interrupt();
}
}
// 如果栅栏已经被打破,抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 最后一个到达栅栏的线程会执行nextGeneration更新generation对象,
// 也就是生成新的一代,重置count,同时会唤醒所有其他await()的线程,
// 这个时候,所有线程走到这里的时候,g != generation这个条件就是成立的,
// 这个方法到这里就返回了
if (g != generation)
return index;
// 发现超时了,打破栅栏。抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
看下这里面两个重要的方法nextGeneration()和breakBarrier()。
// 最后一个到达栅栏的线程会调用这个方法唤醒其他await的线程。同时重新生成下一代
private void nextGeneration() {
// 唤醒所有通过condition.await进入等待的线程
trip.signalAll();
// 设置下一代的count的值
count = parties;
generation = new Generation();
}
// 打破栅栏
private void breakBarrier() {
// 设置generation对象的broken属性为true
generation.broken = true;
// 重置count
count = parties;
// 唤醒所有等待中的线程
trip.signalAll();
}
总结
CyclicBarrier源码相对CountDownLatch真是简单了不少,就是简单了利用了一下ReentrantLock+Condition组合。下面简单总结一下
- 通过构造方法new CyclicBarrier(N)给count属性赋值为N。
- 每个线程来了之后先获取栅栏的锁,对count进行-1操作,代表当前线程已经到达了栅栏。
- 调用Condition的await方法将当前线程挂起,await会释放独占锁。每个线程都会对count减1。
- 当count减为0的时候。说明最后一个线程到达了栅栏。由最后这个线程执行Condition.signalAll方法唤醒所有挂起的线程。
- 线程重新获取到锁后。发现所有线程都已经到达了栅栏,才能继续执行,CyclicBarrier.await方法返回。