CyclicBarrier 是一种线程协作的方法,它可以让一个线程执行到某个点停止,并等待其它线程到达这个点集结完毕再继续运行。
从名字的翻译可以看出 CyclicBarrier是「篱栅」,你可以想象在某个赛道上有比赛,每场比赛需要5匹马到达篱栅才能开始,如下
从这图可以看出,有5匹马要进行比赛,但由于马5没有到达赛场,而导致比赛不能开始,下面是代码演示
public class CyclicBarrierDemo {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 4; ++i) {
threadPool.submit(new Task());
}
threadPool.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "已就位,等待其它马进场");
try {
cyclicBarrier.await(); //await()方法,等待设置的参数的个数的线程集结完毕
System.out.println(Thread.currentThread().getName()
+ "开始赛跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
从打印的结果来看,程序一直在运行,但没有结束。因为我们在上面创建 CyclicBarrier 对象的时候,传入的参数是5,也就是意味着,需要5个线程执行到调用到 await()方法的地方5次,也就是我们上面说的,需要5匹马同时到场,我们才能继续执行后面的代码,但由于我们只提交了4个任务,也就是到达了4匹马,而没有再多创建一个线程,所以这4个线程就在调用 await()方法的地方继续等待,也就是等待最后1匹马到场就可以继续执行。
public class CyclicBarrierDemo {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; ++i) {
threadPool.submit(new Task());
}
threadPool.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "已就位,等待其它马进场");
try {
cyclicBarrier.await(); //await()方法,等待设置的参数的个数的线程集结完毕
System.out.println(Thread.currentThread().getName()
+ "开始赛跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
修改的代码
for (int i = 0; i < 4; ++i) → for (int i = 0; i < 5; ++i)
我们只是多创建提交了一个任务,也就是让第5匹马到达篱栅前, 就达到了比赛开始的条件,也就开始赛跑。
初始化在哪?创建 CyclicBarrier(int parties)的时候,传入的parties参数就是了。后面我们每次调用 await()方法,parties 的个数就减1,直到为0,篱栅就放开,让所有等待执行的任务继续执行。
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)
一个参数的构造器,传入的参数,是在某些位置,需要调用 await()的次数
两个参数的构造器,我这个没用过,等以后有用过来补坑 @TODO
int await()
int await(long timeout, TimeUnit unit)
没有带参数的,每次调用,count减1(count初始化的个数是parties的个数),直到count为0,任务就继续向下执行
带参数的 await(long timeout, TimeUnit unit),第一个参数是设置超时时间的多少,第二个参数设置的是超时时间的单位,当调用 await()的任务在等待其它任务调用 await(),促使count为0后,继续执行后面内容,但由于没有等到最后一个任务调用就超时了,这时候程序会抛出 BrokenBarrierException,并停止运行
cyclicBarrier.await() → cyclicBarrier.await(5, TimeUnit.SECONDS);
//多捕获了一个异常
catch (TimeoutException e) {
e.printStackTrace();}
为什么是抛出 BrokenBarrierException呢?我很疑惑,我还调整了顺序,让捕获TimeoutException在代码放在前面,但还是抛出 BrokenBarrierException,很不解。这里留个坑,等以后想通了来补@TODO
getParties()
getNumberWaiting()
cyclicBarrier.reset();
注:同样是上面的例子,我修改了添加了一部分代码
在这里,我没有调用重置,但是可以继续执行下去,也就是说,当篱栅用过一次后,如果我们不调用 reset(),那么它会默认重置?
我看了源码之后才发现,是的,当最后一次调用了 await()方法后,进入 dowait()方法中里面的某些代码,如下一小段代码的截图
int index = --count; //最后一次调用await()后,--count就为0了,此时的index也是0
if (index == 0) {
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null) //因为我们用两个参数的构造器,所以这个 command为null,忽略这行代码
command.run();
ranAction = true;
nextGeneration(); //这一行是关键
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
下面是 nextGeneration() 的源码
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties; //它重置了count的数量,也就是说,又可以重新使用 await()方法了
generation = new Generation();
}
至于 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();
}
}
从上面的 nextGeneration()方法可以看出,reset()方法是重置了count,使得可以重新使用 await()方法
注:又发现了个问题,就是在多线程的情况下,你调用了 reset()方法的时候,其它线程同时也在调用 await()方法,那么这时候可能抛出BrokenBarrierException异常
public class CyclicBarrierDemo {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; ++i) {
threadPool.submit(new Task());
}
threadPool.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "已就位,等待其它马进场");
try {
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()
+ "开始赛跑");
cyclicBarrier.reset();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
如图所示,在多线程情况下,你一个线程执行 reset()方法进入后,再进入 breakBarrier()方法中,当你把 generation.broken 赋值为 true,然后切换到其它线程,其它线程刚好调用了 await() 方法进入到 dowait()方法中,刚好判断了 generation.broken,因为前面的线程修改了它,所以 generation.broken 为trun,最后抛出了 BrokenBarrierException
欢迎大家关注下个人的「公众号」:独醉贪欢