在所有子线程运行过程中,设置屏障 cyclicBarrier.await(); 突破屏障后,调用CyclicBarrier中带的一个barrierCommand方法。
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
public Soldier(CyclicBarrier cyclic, String soldier) {
this.soldier = soldier;
this.cyclic = cyclic;
}
@Override
public void run() {
try {
//第一个屏障(等待所有子线程一起运行到doWork()之前)
cyclic.await();
doWork();
//第二个屏障(等待所有子线程一起完成doWork()方法)
cyclic.await();
} catch (InterruptedException e) {//在等待过程中,线程被中断
e.printStackTrace();
} catch (BrokenBarrierException e) {//表示当前CyclicBarrier已经损坏.系统无法等到所有线程到齐了.
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + ":任务完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if (flag) {
System.out.println("司令:[士兵" + N + "个,任务完成!]");
} else {
System.out.println("司令:[士兵" + N + "个,集合完毕!]");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
// 初始化屏障器
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合队伍! ");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "报道! ");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
allSoldier[i].start();
}
}
}
CyclicBarrier的应用,也是等待所有子线程工作完成才会继续屏障器后的代码。听起来和CountDownLatch的作用类似啊,但实际上它们还是有很多区别的:
总的来说,CountDownLatch可以做到的事,CyclicBarrier也可以做到,反之不一定,CyclicBarrier相对来说更灵活,应用场景更多。
下面来详细分析一下
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
先来看一下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();
/**
* 表示目前还需要等待多少线程,才能结束当前轮。
*/
private int count;
/*
初始化方法,参数
parties:屏障可以阻拦多少个子线程,
barrierAction:屏障突破后要执行的任务
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
}
初始化主要是指定了可以阻拦的线程个数,以及突破屏障后要执行的任务。
通过这个方法就可以看到屏障器是怎么拦截线程的,又是怎么在最后一个线程到达时,打破屏障的。
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 实际调用的是这个方法,第一个是是否超时的标志false
// 第二个是时间 ,这里就是不设置时间,不会超时
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;
// 如果屏障是由于中断或者出现异常被打破了,那么直接抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果当前线程出现了中断,
if (Thread.interrupted()) {
/*
private void breakBarrier() {
//这个方法内部会设置 g.broken = true;
generation.broken = true;
count = parties;
// 且唤醒所有阻塞的线程,但是唤醒后的每个线程继续执行,还是会根据 if(g.broken) 抛出异常
trip.signalAll();
}
*/
breakBarrier();
throw new InterruptedException();
}
// 线程正常执行到这里,屏障等待拦截的线程减1
int index = --count;
if (index == 0) { // 如果是最后一个线程到达
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run(); //调用屏障解除后的需要运行的任务
ranAction = true;
/*
里面的逻辑主要是,唤醒条件队列中的线程,并且声称一个新一代的屏障器
*/
nextGeneration();
return 0;
} finally {
// 这里是假设command.run()出了异常,那么也调用breakBarrier()方法。
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed) // 如果没有超时,那么把当前线程放到trip条件队列中去,释放锁。其他线程可以获取锁了。
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();
// 被唤醒后,正常返回。 执行完成await()方法
if (g != generation)
return index;
// 如果超时,也会调用 breakBarrier()
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
总结:
CyclicBarrier比较符合人们对屏障拦截器的构想,是通过排他锁,把所有线程全部阻塞在条件队列中,等到最后一个线程到来的时候,就解除屏障,唤醒所有线程继续执行。
CountDownLatch就不同,它是通过共享锁一开始就设置好了里面有多少个共享锁,因此共享锁不释放的话,主线程调用latch.await()就会阻塞。直到最后一个子线程调用latch.countDown()方法,共享锁state==0被释放,并且主线程被唤醒。