概述
CyclicBarrier循环屏障,可以实现让一组线程相互等待,一组线程到达该等待点后同时继续往下执行,CyclicBarrier计数清零,然后进行下一次计数。
可以理解为奥运赛道,因为只有8个赛道,裁判员一次只让8名选手赛跑,8名选手全部就绪,裁判员发号施令,选手们同时起跑,然后让下一组选手入场。
对比CountDownLatch
使用例子
这里只展示一个例子,更多例子可以进入github
public class CyclicBarrierTest {
private final static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
static class Worker implements Runnable {
@Override
public void run() {
try {
System.out.println(currentThread().getName() + ": start");
cyclicBarrier.await();
System.out.println(currentThread().getName() + ": end");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
final int N = 30;
for (int i = 0; i < N; ++i) {
TimeUnit.SECONDS.sleep(1);
new Thread(new Worker()).start();
}
}
}
运行结果:
Thread-0: start
Thread-1: start
Thread-2: start
Thread-2: end
Thread-0: end
Thread-1: end
Thread-3: start
Thread-4: start
Thread-5: start
可以看到上面的结果是每3个释放,原因就是使用了CyclicBarrier,下面我们一起看看源码内部的实现。\
源码结构
public class CyclicBarrier {
//用于循环使用,以及标记本次屏障是否被毁坏
private static class Generation {
boolean broken = false;//标记本次屏障是否被毁坏
}
//对屏障节点加锁
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();
//进行递减计数,初始化值等于parties,递减到0后,释放一组线程,再次赋值为parties
private int count;
。。。
}
可以看到源码使用了ReentrantLock的条件等待Condition,这两个源码在前面文章里已经分析了,就不做详细介绍了。
await方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//如果当代屏障标记已被破坏,抛出BrokenBarrierException异常
if (g.broken)
throw new BrokenBarrierException();
//如果线程被中断
//将本组的屏障标记设为已被破坏并唤醒其他线程
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//将计数递减
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
//减至0则执行前置命令
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//唤醒本组线程,重置count值为parties
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();
}
}
//如果broken为true说明当代屏障已被破坏,抛出BrokenBarrierException异常
if (g.broken)
throw new BrokenBarrierException();
//如果当代屏障已被重置,则返回
if (g != generation)
return index;
//如果本次唤醒等待时间超时唤醒
//将当代屏障设为已被破坏,进行本组唤醒的唤醒,抛出超时异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//解锁
lock.unlock();
}
}
- 将generation赋值给g,暂存当代屏障(因为CyclicBarrier是重复利用的,当计数值减为0后,会重置count值,开启新的一轮屏障,每一轮叫当代屏障)
- count值最开始等于CyclicBarrier的parties的属性,即将目标计数递减,减为0,后判断是否有前置命令(CyclicBarrier构造函数中支持,当达到计数时,先进行一个线程执行方法,即前置命令),再然后调用nextGeneration,唤醒本组线程,重置count值为parties
- 如果count值未减为0,说明本组屏障未全部到达,需要进行等待(一种是超时等待,一种是无限制等待)
- 如果等待过程中发生了中断异常,将当代屏障设为已被破坏,进行本组唤醒的唤醒
- 线程被唤醒后,会判断当代屏障标记是否被置为已破坏
- 如果线程是被等待超时唤醒,设置当代屏障被破坏,抛出超时异常(这里可以看到如果是超时唤醒的CyclicBarrier是不会重置开启新的一轮屏障的)
我们进一步看一下方法调用最多的breakBarrier方法和nextGeneration方法。 breakBarrier方法
private void breakBarrier() {
//将当代屏障设为已被破坏
generation.broken = true;
//count重置为parties
count = parties;
//唤醒本组屏障中的其他等待线程
trip.signalAll();
}
nextGeneration方法
private void nextGeneration() {
// 唤醒所有本轮的屏障线程
trip.signalAll();
//count重置为parties
count = parties;
//设置下一次屏障标志
generation = new Generation();
}
再来看看CyclicBarrier reset方法
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();
}
}
可以看到reset方法就是将当代屏障设为已被破坏,再开启新的一轮屏障(需要注意的是本组的屏障线程后续代码是不会被执行的)
CyclicBarrier就分析到这里了。