介绍
一种同步辅助工具,允许一组线程都等待彼此到达一个共同的障碍点。这个屏障被称为循环,因为它可以在等待的线程被释放后重新使用,之前分析过CountDownLatch,下面说一下两者的区别:
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后(这N个线程并不相互等待,谁先完成都可以)才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程没有到达或完成时,所有的线程都必须互相等待
通过一个简单的例子来理解一下CyclicBarrier,例如运动会中运动员在颁奖时,假如其中一个人在登奖台前准备好,颁奖嘉宾是不能进行颁奖的,需要等待冠军,季军,亚军三位人员都准备就绪后颁奖嘉宾才能进行颁奖,那么这三名运动员都准备就绪这就是一个屏障点,只有三名运动员都准备就绪之后,突破屏障点之后颁奖嘉宾才能进行颁奖操作。
源码解析
主要字段信息
// 用来记录当前屏障是否已经被打破,当broken=true时说明被打破。
private static class Generation {
boolean broken = false;
}
![banjiang.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eed94a7f9bf64187ac37e2af3e665e97~tplv-k3u1fbpfcp-watermark.image)
/** ReentrantLock锁 */
private final ReentrantLock lock = new ReentrantLock();
/** 锁对应的条件 */
private final Condition trip = lock.newCondition();
/** 需要等待线程总数 */
private final int parties;
/* 当突破屏障后执行的内容 */
private final Runnable barrierCommand;
/** 用来记录当前屏障是否已经被打破,当broken=true时说明被打破,这里没有声明成volatile是因为只有一个线程获取 */
private Generation generation = new Generation();
/**
* 还需要多少个线程达到屏障点后才能突破屏障。
*/
private int count;
构造函数
CyclicBarrier基于ReentrantLock的独占锁实现,本质其实也是基于AQS实现,每当有线程调用await方法时,会将当前线程挂起放入到AQS的条件队列中,此时count会减少1,当count减少到0时,表示所有线程已经达到了屏障点,执行通过构造函数传递过来的任务。首先先来看一下构造函数,CyclicBarrier提供了两个构造函数,如下所示:
/**
* 指定相互等待线程数量,并由最后一个进入屏障的线程来执行barrierAction。
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
/**
* 指定相互等待线程。
*/
public CyclicBarrier(int parties) {
this(parties, null);
}
通过构造函数清晰看到有三个变量,count、parties、barrierCommod,其中barrierCommod是通过屏障之后需要执行的任务,为啥会有两个数量?而且两个数量是一样的,大家都知道CyclicBarrier是个可以复用的,如果只设计一个count,当线程调用await时,count进行减1操作,就没办法进行复用操作,parties始终用来记录总的个数,count用来控制还需要多少个线程到达屏障点,当count减少到0时,说明所有线程已经到达了屏障点,parties会重新将值赋值给count值,从而进行复用操作。
await方法
接下来就来分析一下await内部是如何进行操作的?先上源码,如下所示:
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 内部调用了dowait方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
通过源码得知,await其实调用了内部的dowait方法来进行线程等待操作,当抛出异常后会直接抛出Error异常信息。
/**
* 主要障碍代码。
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
// 判断broken是否为true,如果为true说明屏障被打破了,直接抛出异常即可。
if (g.broken)
throw new BrokenBarrierException();
// 响应中断,当线程被中断时,说明屏障被打破了,通知其他线程,当其他线等待的线程再获取到锁时,会直接抛出异常。
if (Thread.interrupted()) {
// 设置broken为true,并通知所有线程等待的线程。
breakBarrier();
throw new InterruptedException();
}
// count进行减少。
int index = --count;
// 当count减少到0时,运行构造函数传递过来的barrierCommand屏障任务。
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
// 运行方法。
command.run();
ranAction = true;
// 通知所有线程突破屏障,循环下一次屏障。
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
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 {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
// 判断是否已经突破一轮屏障,进入了下一轮屏障。
if (g != generation)
return index;
// 如果等待时间小于0抛出异常。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
-
首先上来先进行申请锁操作,也就是同时只有一个线程来进行操作,其他线程需要假如到AQS的等待队列中进行等待。
-
判断broken是否为true,如果为true说明屏障被打破了,直接抛出异常即可,这个是在什么时候会被打破,通过源码可以看到
- 当前线程相应中断请求
- 如果指定了等待时间时,并且传递等待时间是负数,则也会打破屏障。
- 执行构造函数barrierCommand传递过来的任务报错时,也会打破屏障。
-
判断count是否已经减少到0,表示所有线程已经达到了屏障点,由最后一个线程来执行通过构造函数传递过来的barrierCommand任务,调用nextGeneration方法,进行下一轮复用的重置动作。
private void nextGeneration() { // 通知所有等待队列中的线程。 trip.signalAll(); // 重置count值。 count = parties; // 新建一个generation对象,这里为啥要新建一个呢?我们往下面看。 generation = new Generation(); }
- 通知所有条件队列中等待的线程,条件队列中被唤醒的线程,必须先进行获取锁的操作,先将唤醒的线程会被加入到AQS的等待队列中,并获取锁操作,当有线程获取到锁后,会到await的地方继续执行。
- 当执行到 if (g != generation) return index; 此时另外一一个线程已经将generation设置为新的对象,所以会直接返回。
-
breakBarrier是如何控制打破屏障的?
private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); }
- 将generation的broken设置为true,count设置为parties。
- 唤醒所有的线程,唤醒所有线程的时候会执行第61行的代码 if (g.broken) throw new BrokenBarrierException();这时候if语句中为true,则会直接抛出异常打破屏障。
总结
- 一种同步辅助工具,允许一组线程都等待彼此到达一个共同的障碍点。
- 这个屏障被称为循环,因为它可以在等待的线程被释放后重新使用。
- 内部使用的依然是AQS,不过比起CountDownLatch来讲,CyclicBarrier是内部自己实现的,而CountDownLatch利用的AQS同步锁state状态值来控制。
- CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后(这N个线程并不相互等待,谁先完成都可以)才能执行。
- CyclicBarrier : N个线程相互等待,任何一个线程没有到达或完成时,所有的线程都必须互相等待
喜欢的朋友可以关注我的微信公众号,不定时推送文章