CyclicBarrier 概念:
CyclicBarrier 字面意思是环栅栏,是 JUC 下的一个并发工具,跟 CountDownLatch 很相似,都可以使线程先等待然后再执行,但是它的功能比 CountDownLatch 更加复杂和强大, CountDownLatch 是一个或者多个线程等待另外一批线程执行完毕后,在接着执行,而 CyclicBarrier 是等待一批线程到达某个状态之后再同时开始执行,回环的意思是当所有的线程被释放后,CyclicBarrier 可以被重启,也就是可以重复使用。
知识储备传送门:
深入理解 AbstractQueuedSynchronizer(AQS)【源码分析】
深入理解 ReentrantLock 【源码分析】
CAS的使用以及底层原理详解
深入理解 CountDownLatch 【源码分析】
CyclicBarrier 属性、构造方法源码分析:
public class CyclicBarrier {
//内部类 每次使用CyclicBarrier 都会生成一个Generation实例 每当屏障被处罚或者重置时候 对应的Generation就会发生变化
private static class Generation {
boolean broken = false;
}
//ReentrantLock 重入锁 保护屏障入口的锁
private final ReentrantLock lock = new ReentrantLock();
//等待屏障触发的条件 用于线程的等待或者唤醒
private final Condition trip = lock.newCondition();
//源码直译是聚会的人数 也就是屏障拦截的线程数
private final int parties;
//跳闸时候执行的命令 也就是唤醒的时候执行的命令
private final Runnable barrierCommand;
//当前代 每当栅栏失效或者开闸之后都会自动替换掉 从而实现重置的功能
private Generation generation = new Generation();
//还要等待的线程数量 也就是还能阻塞的线程数 还有多少线程为到屏障前
private int count;
//更新屏障行程状态并唤醒所有线程 仅在持有锁时调用 是开启下一代的方法
private void nextGeneration() {
// signal completion of last generation
//唤醒所有线程
trip.signalAll();
// set up next generation
//恢复count值,开启新的一代
count = parties;
generation = new Generation();
}
//将当前屏障生成设置为已破坏并唤醒所有线程 仅在持有锁时调用。
private void breakBarrier() {
//将这一代中的 broken 设置为true 表示这一代是被打破了的 再来到这一代的线程 直接抛出异常
generation.broken = true;
//重置 count
count = parties;
//唤醒所有线程 调用的是 AbstractQueuedLongSynchronizer.ConditionObject#signalAll
trip.signalAll();
}
//构造方法
public CyclicBarrier(int parties, Runnable barrierAction) {
//如果拦截的线程数量小于0 就抛出异常
if (parties <= 0) throw new IllegalArgumentException();
//拦截的线程数量等于传入的数量
this.parties = parties;
//还要等待的线程数量
this.count = parties;
//设置所有线程都到达屏障位置要执行的操作
this.barrierCommand = barrierAction;
}
//构造方法 创建拦截指定数量线程的CyclicBarrier
public CyclicBarrier(int parties) {
this(parties, null);
}
}
类属性解析:
- Generation:代,在 CyclicBarrier 中同一批线程属于同一代,当屏障拦截的线程数 parties 都到达屏障前,此时会更新到下一代,broken 标识当前代的状态,默认 false ,false 表示没有被打破,true 表示被打破了。
- parties:屏障拦截的线程数。
- count:没有达到屏障前的线程数,也就是要等待的线程数。
- trip :等待屏障触发的条件,用于线程的等待或者唤醒。
- lock :重入锁,保护屏障入口的锁。
- barrierCommand:屏障将要放开的时候执行的操作,也就是所有等待的线程都到达屏障了之后的操作。
- nextGeneration:开启下一代的方法,并唤醒所有等待的线程。
- breakBarrier:将当前屏障生成设置为已破坏,并唤醒所有线程,仅在持有锁时才能调用。
AbstractQueuedLongSynchronizer.ConditionObject#signalAll 方法源码分析:
//AbstractQueuedLongSynchronizer.ConditionObject#signalAll 唤醒所有的线程
public final void signalAll() {
//当前线程是否占有锁 必须占有锁才可以调用 doSignalAll 方法
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取头节点
Node first = firstWaiter;
//头节点不为空 表示是正常的条件队列
if (first != null)
//执行唤醒操作
doSignalAll(first);
}
//java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject#doSignalAll 删除并转移所有节点 其实就是唤醒所有节点并删除
private void doSignalAll(Node first) {
//最后一个节点 第一个节点都赋值为 null
lastWaiter = firstWaiter = null;
//循环唤醒节点
do {
//获取头节点的下一个节点
Node next = first.nextWaiter;
//头节点的下一个赋值为 null
first.nextWaiter = null;
//
transferForSignal(first);
first = next;
} while (first != null);
}
//java.util.concurrent.locks.AbstractQueuedLongSynchronizer#transferForSignal 将节点从条件队列传输到同步队列 如果成功则返回 true。
final boolean transferForSignal(Node node) {
//将节点状态设置为 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
//设置失败
return false;
//将当前节点添加到同步阻塞队列中 AQS 源码中分析过 enq 方法
Node p = enq(node);
//获取节点的状态
int ws = p.waitStatus;
//状态大于0 表示节点已经取消了 或者尝试设置节点P的状态为 SIGNAL 失败了(表示下一个节点要停止阻塞 需要唤醒他了)
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒当前线程上的节点
LockSupport.unpark(node.thread);
return true;
}
//AbstractQueuedLongSynchronizer#enq 初始化队列 并把node 节点加入队列作为队列的尾节点
private Node enq(final Node node) {
//自旋
for (;;) {
//把尾节点 赋值给 t
Node t = tail;
//判断尾节点是否为空
if (t == null) { // Must initialize
//尾节点为空 创建一个新的空节点 并使用 CAS 将创建的空节点设置为队列头节点
if (compareAndSetHead(new Node()))
//头节点设置成功后 也就是队列初始化成功后 把队列的尾节点也指向刚刚创建的空的头节点
tail = head;
} else {
//尾节点不为空 表示队列初始化完成了 至少是第二次循环了 将node节点的前驱指针指向当前队列的尾节点
node.prev = t;
//使用 CAS 用设置node 节点为新的尾节点
if (compareAndSetTail(t, node)) {
//设置成功后 将之前的尾节点 t 的后驱指针指向 node 节点
t.next = node;
//node 节点成功加入到队列尾部
return t;
}
}
}
}
CyclicBarrier #await 方法源码分析:
调用 await 方法的线程告诉 CyclicBarrier 我已经到达屏障位置,然后阻塞当前线程等待其他线程的到来,CyclicBarrier 提供带超时的 await 方法和不带超时时间的await方法,这两个方法最后都会调用 dowait 方法。
//不带超时的await方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
//真正干活的方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//带超时时间的 await 方法
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
CyclicBarrier #dowait方法源码分析:
dowait 方法是真正做到让线程在屏障前等待的方法,也是 CyclicBarrier 的核心方法。
//实现 栅栏/屏障的核心方法
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//获取重入锁
final ReentrantLock lock = this.lock;
//锁定
lock.lock();
try {
//获取当前代
final Generation g = generation;
//为ture 表示这一代是被打破了的 抛出异常
if (g.broken)
throw new BrokenBarrierException();
//获取当前线程的打断状态
if (Thread.interrupted()) {
//状态是被打断了的 将当前这一代设置为破坏了的 唤醒所有等待的线程 breakBarrier 源码上面分析过
breakBarrier();
//抛出异常
throw new InterruptedException();
}
//count 还能阻塞的线程减一
int index = --count;
if (index == 0) { // tripped
//如果还能阻塞的线程为0 表示这是最后一个线程了 其他的线程都到了 栅栏/屏障前了
//任务是否被执行的标志
boolean ranAction = false;
try {
//所有线程到达栅栏/屏障前要执行的操作
final Runnable command = barrierCommand;
if (command != null)
//不为空 则开始执行
command.run();
//任务执行标志被设置为 false
ranAction = true;
//进入下一代 nextGeneration 方法上面分析过源码
nextGeneration();
//返回
return 0;
} finally {
//所有线程到达栅栏/屏障前要执行的操作发生了异常要走到这里来
if (!ranAction)
//任务没有被执行 将当前这一代设置为破坏了的 唤醒所有等待的线程 breakBarrier 源码上面分析过
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
//自旋 非最后一个线程会走到这里来
for (;;) {
try {
// 没有等待超时时间 false 有等待超时时间 true
if (!timed)
//没有等待超时时间 等待直到被唤醒
trip.await();
else if (nanos > 0L)
//超时时间大于0 等待
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//中断异常
if (g == generation && ! g.broken) {
//当前代没有变化 且当前代没有被打断 则将当前屏障设置为已破坏 打断屏障
breakBarrier();
throw ie;
} else {
//当前代发生了变化 或者 代已经被打破了
//代发生了变化 那表示进入了下一代 唤醒后走正常逻辑 标记一下中断过
//代没有发生变化 但是代已经被打破了 记录下中断标记
Thread.currentThread().interrupt();
}
}
//来到这里的几种可能
//开启了新一代 broken breakBarrier 方法会把 broken 设置为true 唤醒所有等待的线程
//等待超时 获取到锁后 也会走到这里
//当前代的打破情况
if (g.broken)
//被打破 抛出异常
throw new BrokenBarrierException();
if (g != generation)
//当前代已经发生了变化 表示最后一个线程到位了 触发了下一代的逻辑 因此要唤醒 条件队列中的所有线程
//返回当前线程的 index
return index;
if (timed && nanos <= 0L) {
//有等待时间 且等待时间小于等于0 那当前线程就不等了 打破规则 屏障
breakBarrier();
//抛出超时异常
throw new TimeoutException();
}
}
} finally {
//解锁
lock.unlock();
}
}
CyclicBarrier #reset 方法源码分析:
//重置栅栏/屏障
public void reset() {
//获取重入锁
final ReentrantLock lock = this.lock;
//锁定
lock.lock();
try {
//打破屏障(当前代) 并唤醒所有的线程
breakBarrier(); // break the current generation
//进入下一代
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
我们知道 CyclicBarrier 具备重用功能,reset 方法就是实现重用功能的实现,可以看到重用功能的代码实现非常简单,就是打破屏障进入下一代即可。
CyclicBarrier 总结:
- CyclicBarrier#await 方法会让一组线程停留在栅栏/屏障处,只有当大家都到了栅栏/屏障处,才会打开栅栏同时往下走。
- CyclicBarrier 和 CountDownLatch、Semaphore 不一样,没有直接使用 AQS,而是基于 ReentrantLock 及 Condition 来实现的。
如有错误的地方欢迎指出纠正。