CyclicBarrier简介
CyclicBarrier也是基于ReentrantLock和Condition的一个同步工具类,它的作用是让一些线程到达某个公共屏障点时,等待未到达的线程。当所有线程到达屏障点时,继续往下执行。
先看一个例子
public class CyclicBarrierDemo implements Runnable {
private CyclicBarrier cyclicBarrier;
public CyclicBarrierDemo(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " running ");
try {
cyclicBarrier.await();
System.out.println("all arrive at " + System.currentTimeMillis());
} catch (Exception e) {
}
}
public static void main(String[] args) throws IOException {
CyclicBarrier barrier = new CyclicBarrier(5);
for (int i = 0; i < 5; i++) {
new Thread(new CyclicBarrierDemo(barrier)).start();
}
System.in.read();
}
}
实现原理
和其他同步工具不同的是,它的内部并没有继承AQS的内部类Sync。
private static class Generation {
// 当前的屏障是否重置,为true的话,说明这个屏障已经损坏,当某个线程await的时候,直接抛出异常
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();
// 还能阻塞的线程数
private int count;
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
# await
CyclicBarrier最重要的方法就是await了,它的作用在例子中已经体会到了,下面进入代码看看它到底是如何实现的。
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();
}
// 前面都是条件判断,等待的线程数减1
int index = --count;
if (index == 0) { // count减为0,那么需要唤醒所有的线程,并且这个将屏障重置
boolean ranAction = false;
try {// 执行任务
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();//
return 0;
} finally {
if (!ranAction)// 任务没能成功执行,则重置屏障
breakBarrier();
}
}
for (;;) {// 如果计数器不为0则执行此循环
try {
if (!timed)// 如果非定时等待,调用condition.wait()
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();
}
}
逻辑较长总结一下:
- 如果当前线程不是最后一个线程,也就是说count不为0时,根据调用的await是否为限时等待,调用condition的await方法,此时线程释放锁,进入Condition的等待队列(AQS那篇文章中讲过),当它被唤醒时需要判断当前屏障是否被破坏,并且判断屏障是否换代。
总结一下什么时候会破坏屏障:
a. 某一个线程被中断
b. 屏障任务执行失败
c. 屏障换代
d. 等待时间设置小于0
e. 调用reset方法重置
什么时候会换代:
a. 当所有线程都到达屏障,并且任务成功执行
b. 调用reset方法
2. 如果当前线程是最后一个到达屏障的线程,那么先执行任务,然后对屏障换代,执行nextGeneration方法。
private void nextGeneration() {
// 唤醒所有阻塞线程
trip.signalAll();
// 重置线程数
count = parties;
// 新建generation
generation = new Generation();
}
private void breakBarrier() {
generation.broken = true;
//设置计数器的值为需要拦截的线程数
count = parties;
//唤醒所有线程
trip.signalAll();
}
至此CyclicBarrier已经讲述完毕,最后一点CyclicBarrier可以复用,即当屏障使用完一次之后,自动的重置线程数。但是CountDownLach则不会重置计数器。