CyclicBarrier的工作原理
CyclicBarrier大致是可循环利用的屏障,顾名思义,这个名字也将这个类的特点给明确地表示出来了。首先,便是可重复利用,说明该类创建的对象可以复用;其次,屏障则体现了该类的原理:每个线程执行时,都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。
这里介绍CyclicBarrier的两个构造函数:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。
实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。
源码
CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点。与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障(Barrier)。
CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。
主要方法
//parties表示屏障拦截的线程数量,当屏障撤销时,先执行barrierAction,然后在释放所有线程
public CyclicBarrier(int parties, Runnable barrierAction)
//barrierAction默认为null
public CyclicBarrier(int parties)
/*
*当前线程等待直到所有线程都调用了该屏障的await()方法
*如果当前线程不是将到达的最后一个线程,将会被阻塞。解除阻塞的情况有以下几种
* 1)最后一个线程调用await()
* 2)当前线程被中断
3)其他正在该CyclicBarrier上等待的线程被中断
4)其他正在该CyclicBarrier上等待的线程超时
5)其他某个线程调用该CyclicBarrier的reset()方法
*如果当前线程在进入此方法时已经设置了该线程的中断状态或者在等待时被中断,将抛出InterruptedException,并且清除当前线程的已中断状态。
*如果在线程处于等待状态时barrier被reset()或者在调用await()时 barrier 被损坏,将抛出 BrokenBarrierException 异常。
*如果任何线程在等待时被中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。 *如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作(barrierAction),那么在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
*
*返回值为当前线程的索引,0表示当前线程是最后一个到达的线程
*/
public int await() throws InterruptedException, BrokenBarrierException
//在await()的基础上增加超时机制,如果超出指定的等待时间,则抛出 TimeoutException 异常。如果该时间小于等于零,则此方法根本不会等待。
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
//将屏障重置为其初始状态。如果所有参与者目前都在屏障处等待,则它们将返回,同时抛出一个BrokenBarrierException。
public void reset()
类结构
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock(); //可重入锁
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties; //拦截的线程数量
/* The command to run when tripped */
private final Runnable barrierCommand; //当屏障撤销时,需要执行的屏障操作
//当前的Generation。每当屏障失效或者开闸之后都会自动替换掉。从而实现重置的功能。
private Generation generation = new Generation();
/**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
*/
private int count; //还能阻塞的线程数(即parties-当前阻塞的线程数),当新建generation或generation被破坏时,count会被重置。因为对Count的操作都是在获取锁之后,所以不需要其他同步措施。
//静态内联类
private static class Generation {
boolean broken = false; //当前的屏障是否破坏
}
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 {
//保存此时的generation
final Generation g = generation;
//判断屏障是否被破坏
if (g.broken)
throw new BrokenBarrierException();
//判断线程是否被中断,如果被中断,调用breakBarrier()进行屏障破坏处理,并抛出InterruptedException
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count; //剩余count递减,并赋值给线程索引,作为方法的返回值
//如果线程索引将为0,说明当前线程是最后一个到达的线程。执行可能存在的屏障操作 barrierCommand,设置下一个Generation。相当于每次开闸之后都进行了一次reset。
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run(); //同步执行barrierCommand
ranAction = true;
nextGeneration(); //执行成功设置下一个nextGeneration
return 0;
} finally {
if (!ranAction) //如果barrierCommand执行失败,进行屏障破坏处理
breakBarrier();
}
}
//如果当前线程不是最后一个到达的线程
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await(); //调用Condition的await()方法阻塞
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos); //调用Condition的awaitNanos()方法阻塞
} 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();
//这种捕获了InterruptException之后调用Thread.currentThread().interrupt()是一种通用的方式。其实就是为了保存中断状态,从而让其他更高层次的代码注意到这个中断。
}
}
//如果屏障被破坏,当前线程抛BrokenBarrierException
if (g.broken)
throw new BrokenBarrierException();
//如果已经换代,直接返回index(last thread已经执行的nextGeneration,但当前线程还没有执行到该语句)
if (g != generation)
return index;
//超时,进行屏障破坏处理,并抛TimeoutException
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock(); //释放锁
}
}
//将当前屏障置为破坏状态、重置count、并唤醒所有被阻塞的线程。
//必须先获取锁,才能调用此方法
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
//唤醒trip上等待的所有线程,设置下一个Generation
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
//重置屏障,先进行屏障破坏处理,再设置下一代generation
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
举例说明
如果一个寝室四个人约好了去球场打球,由于四个人准备工作不同,所以约好在楼下集合,并且四个人集合好之后一起出发去球场。
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));
private static class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "开始从宿舍出发");
try {
cb.await();
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "从楼底下出发");
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "到达操场");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String[] str = {"李明", "王强", "刘凯", "赵杰"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,现在开始打球");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
王强开始从宿舍出发
刘凯开始从宿舍出发
李明开始从宿舍出发
赵杰开始从宿舍出发
寝室四兄弟一起出发去球场
李明从楼底下出发
刘凯从楼底下出发
王强从楼底下出发
赵杰从楼底下出发
赵杰到达操场
刘凯到达操场
王强到达操场
李明到达操场
四个人一起到达球场,现在开始打球
CyclicBarrier进行复用
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));
private static class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "开始从宿舍出发");
try {
cb.await();
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "从楼底下出发");
TimeUnit.SECONDS.sleep(1);
System.out.println(name + "到达操场");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String[] str = {"李明", "王强", "刘凯", "赵杰"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,现在开始打球");
System.out.println();
System.out.println("现在对CyclicBarrier进行复用.....");
System.out.println("又来了一拨人,看看愿不愿意一起打:");
} catch (InterruptedException e) {
e.printStackTrace();
}
String[] str1= {"王二","洪光","雷兵","赵三"};
for (int i = 0; i < 4; i++) {
threadPool.execute(new MyThread(str1[i]));
}
try {
Thread.sleep(4000);
System.out.println("四个人一起到达球场,表示愿意一起打球,现在八个人开始打球");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
李明开始从宿舍出发
刘凯开始从宿舍出发
王强开始从宿舍出发
赵杰开始从宿舍出发
寝室四兄弟一起出发去球场
李明从楼底下出发
赵杰从楼底下出发
刘凯从楼底下出发
王强从楼底下出发
李明到达操场
赵杰到达操场
刘凯到达操场
王强到达操场
四个人一起到达球场,现在开始打球
现在对CyclicBarrier进行复用…
又来了一拨人,看看愿不愿意一起打:
王二开始从宿舍出发
洪光开始从宿舍出发
赵三开始从宿舍出发
雷兵开始从宿舍出发
寝室四兄弟一起出发去球场
雷兵从楼底下出发
赵三从楼底下出发
王二从楼底下出发
洪光从楼底下出发
洪光到达操场
赵三到达操场
王二到达操场
雷兵到达操场
四个人一起到达球场,表示愿意一起打球,现在八个人开始打球
CountDownLatch和CyclicBarrier的比较
1.CountDownLatch是线程组之间的等待,即一个(或多个)线程等待N个线程完成某件事情之后再执行;而CyclicBarrier则是线程组内的等待,即每个线程相互等待,即N个线程都被拦截之后,然后依次执行。
2.CountDownLatch是减计数方式,而CyclicBarrier是加计数方式。
3.CountDownLatch计数为0无法重置,而CyclicBarrier计数达到初始值,则可以重置。
4.CountDownLatch不可以复用,而CyclicBarrier可以复用。
5.CountDownLatch基于AQS;CyclicBarrier基于锁和Condition。本质上都是依赖于volatile和CAS实现的