文章目录
前言
CyclicBarrier的字面意思是可循环使用(Cycli)的屏障(Barrier)。它的作用就是,让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障踩会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier源码分析(基于JDK1.8)
CyclicBarrier整体结构
一、参数
/*可重入锁*/
private final ReentrantLock lock = new ReentrantLock();
/*等待队列*/
private final Condition trip = lock.newCondition();
/*参与等待的线程数量*/
private final int parties;
/*当所有线程达到屏障点之后,首先执行的任务*/
private final Runnable barrierCommand;
/*实际中仍在等待的线程数,每当有一个线程到达屏障点,count值就会减少一;
当一次新的运算开始后,count会被重置为parties*/
private int count;
/*表示当前屏障的状态,true被破坏,false没有被破坏*/
private Generation generation = new Generation();
二、构造函数
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
参数:
- parites:参与相互等待的线程数量
- barrierAction:指定所有线程到达屏障点之后,首先执行的操作,该操作由最后一个进入屏障点的线程执行。
这是与CountDownLatch不同的一个点。CyclicBarrier可以指定所有线程到达同步点之后第一个执行的任务(该任务具备了最高的优先级),而CountDownLatch并不可以。
三、CyclicBarrier的核心API
3.1 await
//该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态
//直到所有线程都到达屏障点,当前线程才会被唤醒
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
调用await()方法表示当前线程已经到达了屏障点(同步点),若有其他参与互相等待的线程没有到达屏障点,那么当前线程会被阻塞。
我们来看一下dowait(boolean timed, long nanos)
方法的实现。
参数:
- timed:true表示当前线程进入timed_waiting状态,false表示当前线程进入waiting状态。
- nanos:线程超时等待的时长
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final CyclicBarrier.Generation g = generation;
/*如果当前屏障被破坏,抛出异常*/
if (g.broken)
throw new BrokenBarrierException();
/*如果当前调用await函数的线程被中断,调用breakBarrier函数并抛出异常*/
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
/*由于当前线程已经达到屏障点,那么等待到达屏障点的线程数量减少一*/
int index = --count;
/*index为0说明所有的线程都到达了屏障点*/
if (index == 0) { // tripped
/*判断是否有优先执行的任务*/
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
/*有优先执行的任务,那么由当前调用await()的线程执行该任务*/
if (command != null)
command.run();
ranAction = true;
/*执行唤醒全部线程的操作*/
nextGeneration();
return 0;
} finally {
/*说明在执行barrierCommand的时候发生了异常*/
if (!ranAction)
breakBarrier();
}
}
/*若所有的线程还未全部到达屏障点*/
for (;;) {
try {
/*判断阻塞方式,并将当前线程加入Condition队列中*/
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//若遭遇中断
//这个中断异常是从Condition中抛出来的,对应THROW_IN
if (g == generation && ! g.broken) {
breakBarrier();
/*将当前异常直接抛出*/
throw ie;
} else {
/*
* 这里两种情况:
* 1. g!=generation,说明被reset过
* 2. g==generation&&g.broken, 说明屏障被破坏了
* 对于第二种情况,当前屏障已经被破坏,说明这次中断是属于这一轮等待到达屏障点的,接下来的操作会直接抛出异常(对应if(g.broken))
* 对于第一种情况,由于g!=generation, 说明reset了或者这一轮到达屏障点的任务已经完成,那么接下来的操作会直接退出循环(对应if(g!=generation)),所以这次中断是属于后续执行的操作,我们把中断外抛。
*/
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();
}
}
简单梳理下dowait
的流程:
- 当前操作需要进行加锁
- 判断当前屏障是否被打破或者当前线程被中断,抛出异常。
- 若步骤二均未发生,判断当前线程是否是到达屏障点的最后一个线程,如果是,执行BarrierCommand(如果有),唤醒全部在Condition队列上等待的线程,并重置CyclicBarrier的状态,更新generation。
- 若当前线程不是最后一个到达屏障点的线程,那么当前线程被挂起,直至被唤醒/中断/超时。
我们来看一下nextGeneration中出现的相关函数。
nextGeneration():唤醒所有Condition队列中的线程,并重置CyclicBarrier的状态
private void nextGeneration() {
/*唤醒Condition队列中的所有线程*/
trip.signalAll();
/*更新count为初始值*/
count = parties;
/*更新generator*/
generation = new CyclicBarrier.Generation();
}
breakBarrier(): 唤醒所有Condition队列中的线程,并重置CyclicBarrier的成员变量count,并且把generation的broken变量设置为true,表示当前屏障被破坏。
private void breakBarrier() {
/*表示当前屏障被破坏*/
generation.broken = true;
/*更新count为初始值*/
count = parties;
/*唤醒Condition队列中的所有线程*/
trip.signalAll();
}
上述出现的await、signalAll是AQS的Condition队列的内容,具体可以参考JDK源码分析系列 AQS续篇Condition源码浅析,这里不再赘述。
3.2. 其他API
/*重置CyclicBarrier*/
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
/*判断屏障是否被破坏*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
/*获取到达屏障点的线程数量*/
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
以上几个函数一眼就能看明白,就不多做分析了。
这便是CyclicBarrier源码的全部分析,其实很简单,没有多大难度,其中关于AQS的知识自行了解。
四、应用
demo:
public class CyclicBarrierDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
final CyclicBarrier barrier = new CyclicBarrier(3);
new Thread(()->solve(barrier)).start();
new Thread(()->solve(barrier)).start();
barrier.await();
System.out.println(Thread.currentThread().getName() + "线程执行完毕");
}
private static void solve(CyclicBarrier barrier) {
try {
System.out.println(Thread.currentThread().getName());
barrier.await();
System.out.println(Thread.currentThread().getName() + "执行完毕");
}catch (Exception e){
}
}
}
/*
输出:
t1
main
t2
t1线程执行完毕
t2线程执行完毕
main线程执行完毕
*/
通过上述例子我们就可以明白CyclicBarrier的具体功能。
t1执行输出当前线程名操作,到达屏障点后被阻塞。
main线程输出当前线程名操作,到达屏障点后被阻塞。
t2执行输出当前线程名操作并达到屏障点,至此,所有线程达到屏障点,会唤醒t1和main线程执行接下来的操作。
demo2:
public class CyclicBarrierDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
final CyclicBarrier barrier = new CyclicBarrier(3, new FirstTask());
new Thread(()->solve(barrier), "t1").start();
new Thread(()->solve(barrier), "t2").start();
//LockSupport.park(mainThread);
System.out.println(Thread.currentThread().getName());
barrier.await();
System.out.println(Thread.currentThread().getName() + "线程执行完毕");
}
private static void solve(CyclicBarrier barrier) {
try {
System.out.println(Thread.currentThread().getName());
barrier.await();
System.out.println(Thread.currentThread().getName() + "线程执行完毕");
}catch (Exception e){
}
}
static class FirstTask implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程正在执行当前具有最高优先级的任务");
}
}
//demo
static class BankWaterService implements Runnable{
private CyclicBarrier barrier = new CyclicBarrier(4, this);
private Executor executor = Executors.newFixedThreadPool(4);
private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<>();
private void count(){
for (int i = 0; i < 4; i++) {
executor.execute(()->{
sheetBankWaterCount.put(Thread.currentThread().getName(), 4);
try {
barrier.await();
}catch (Exception e){
//todo
}
});
}
}
@Override
public void run() {
int result = 0;
for (Integer i : sheetBankWaterCount.values()){
result += i;
}
System.out.println(result);
}
}
@Test
public void test(){
BankWaterService service = new BankWaterService();
service.count();
}
}
/*
输出:
t1
main
t2
t2线程正在执行当前具有最高优先级的任务
t2线程执行完毕
t1线程执行完毕
main线程执行完毕
*/
我们利用CyclicBarrier来执行BarrierCommand任务,该任务具有最高的优先级,当全部线程到达屏障点后,该任务会优先执行,从源码看该任务由最后一个到达屏障点的线程来进行执行。
以上便是CyclicBarrier的一些实际应用。
五、和CountDownLatch的区别
这个也算一个老生常谈的问题了吧。
在JDK源码系列 AQS续篇共享锁源码实现分析中我们已经分析过CountDownLatch,这两个同步辅助器的区别我们来说一下。
- 在CountDownLatch中,我们利用到是AQS的共享锁;在CyclicBarrier中,我们利用到的是AQS的独占锁和Condition队列。
- CountDownLatch只能够使用一次,而CyclicBarrier是可以循环利用的,通过reset重置来达到循环利用的目的。
- CyclicBarrier可以指定任务优先执行,而CountDownLatch是不行的。
- CyclicBarrier还提供其他有用的方法,比如 getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken()方法用来判断屏障是否被损坏,中断/超时。
- 这个点我想说一下,看过CountDownLatch我们知道,调用countDown()方法的线程之后是可以继续执行的,并不会被阻塞。而在CyClicBarrier中,但线程到达屏障点且当前线程不是最后一个达到屏障点的线程,那么当前线程线程会被阻塞,不能执行接下来的操作,只有等到最后一个到达屏障点的线程唤醒所有的线程,才可以继续接下来的操作。我觉得这里也是应该注意一下的。