简单介绍
CyclicBarrier是juc下的多线程工具类,翻译过来就是循环栅栏的意思,在使用CyclicBarrier的时候需要指定线程数
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
在多线程协作的过程中,只有当指定个数的线程都到达了预定位置线程才会往下执行。下面从源码来分析一下它的工作原理
工具初始化
当调用如下代码初始化CyclicBarrier工具类的时候,实际上是指定了需要协作的线程数
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
CyclicBarrier的构造方法仅仅是记录我们传入的3
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties; //指定的线程数,
this.count = parties; //初始化为指定的线程数
//指定的线程数被记录的两次是因为 parties是final的,因为是循环栅栏,
//所以需要在栅栏打开后再次将count赋值为parties,实际多线程使用过程中我们
//只需要关注count便可
this.barrierCommand = barrierAction; //打开栅栏后需要执行的runnable
}
CyclicBarrier类的成员变量如下
/** 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;
/** The current generation */
private Generation generation = new Generation();
到目前为止,不用过多关注以上的代码,只需要记住在我们初始化CyclicBarrier的时候指定了一个线程数
测试类
这是用来测试CyclicBarrier工作原理的代码
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
System.out.println("");
new Thread(() -> {
System.out.println("线程1开始等待指挥员到来");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1等待结束,开始执行任务");
}).start();
new Thread(() -> {
System.out.println("线程2开始等待指挥员到来");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2等待结束,开始执行任务");
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("指挥员就位");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("任务开始");
}
}
执行结果:
线程1开始等待指挥员到来
线程2开始等待指挥员到来
指挥员就位
任务开始
线程1等待结束,开始执行任务
线程2等待结束,开始执行任务
从上面我们能看到,整个测试程序只调用了CyclicBarrier的一个方法:await(),所以只需要从await方法入手
await方法
await()方法直接调用内部的dowait方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
原因是CyclicBarrier提供的await方法重载了两种方法,一种是带超时时间的,一种是不带超时时间的,以下是带超时时间的方法
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
所以我们需要研究dowait()方法
dowait方法
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//首先加锁
lock.lock();
try {
//记录当前CyclicBarrier的generation成员变量,以待后用
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
//前面两步不用看,看源码需要忽略部分细节,而理清整体脉络,
//第一次进入dowait的线程肯定不会抛出异常,所以没必要看
if (Thread.interrupted()) {
//判断如果当前线程在等待中被中断便跳出栅栏,这个也不是主线逻辑,忽略
breakBarrier();
throw new InterruptedException();
}
//从这里开始,是正常线程走的逻辑
//首先将count-1,记录剩余的数量,count就是我们初始化指定的线程数
int index = --count;
//如果index==0,这意味着最后一个线程执行到这里才会index==0,记住这里,先跳到后面
if (index == 0) {
//最后一个线程会直接走到这里
//首先标记是否需要执行runnable的方法(初始化的时候可以指定)
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
/**以上执行了runnable的run方法
* 并且是最后一个线程直接执行的,并没有开启新的线程异步执行哦
* 执行nextGeneration()后直接return 0了,所以nextGeneration()方法
* 里是唤醒其他等待线程,并且重置循环栅栏,该方法代码贴在后面
* 至此整个循环栅栏工作已经完成
**/
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// for循环,意味着除了最后一个线程,都将在这循环等待
for (;;) {
try {
//trip.await trip是lock.condition(),这里就是直接等待并且释放锁
//这里的if else只是判断是直接等待还是等待一段时间
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} 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();
}
}
if (g.broken)
throw new BrokenBarrierException();
/**这里是关键,因为这里有return,什么时候return?
* 就是g != generation的时候,前面方法开始的时候每个线程会记录这个线程刚进入
* dowait方法的时候的generation变量,而所有等待的线程都不会改这个变量,只有
* 最后一个线程会改这个变量,只有在最后一个线程处理完,并且改了generation以后
* 其他在等待的线程才能正常退出,所以让我们回到前面if (index == 0)方法
**/
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
总结
CyclicBarrier的await()方法的流程已经分析完毕了,最后用文字来总结一下它的工作流程
- 首先初始化CyclicBarrier指定需要拦截的线程数,指定的线程数记做count
- 然后每个线程执行await方法的时候先加锁然后将count -1,如果count-1≠0那么就需要执行等待
- 需要等待的线程执行lock.condition().await()方法循环等待并且释放锁
- 最后一个线程到达的时候count-1=0,便会执行指定的runnable的run方法
- 执行runnable的run方法后重置count和genaration,然后唤醒其他等待的线程,当前线程退出
- 等待线程被唤醒,判断generation被更改,退出循环等待