(一)概念简介
CyclicBarrier是一个可循环使用的屏障锁,依赖于ReentrantLock和Condition来实现阻塞和唤醒,主要是分为普通阻塞和超时阻塞,利用构造方法初始化指定parties(屏障数)和count(剩余屏障数),只需要使用await即可让count值持续递减。基于Condition条件,屏障锁对于屏障数分为多组,每组中使用ReentrantLock产生的condition条件进行阻塞,中途发生中断或异常会直接唤醒当组被阻塞的线程并重置下组的初始条件。
(二)使用场景
当主线程进行执行时,利用构造方法初始化一个屏障数和剩余屏障数,子线程调用await方法进行阻塞自身,待到当前组剩余屏障数为0时则唤醒当前组所有被阻塞的线程,然后再执行子线程中的剩余逻辑。
(1)多个线程在执行过程中需要达到某一条件后再统一执行剩下的各自线程不一样的逻辑场景如预初始化辅助类和共同逻辑业务数据计算。
(2)多个不同的线程任务,将自身负责的加载配置完成后等待打破屏障后再执行剩余的各自逻辑或统一逻辑如汇总后的日志;
注意:为了程序的健壮性,尽量给出合适的时间,防止多组线程数未达到屏障数导致无法唤醒如:最后一组线程数不够屏障数会导致程序无法结束。
(三)特点
(1)子线程调用await方法会阻塞自身,不会阻塞主线程;
(2)可循环使用该屏障,每组都基于ReentrantLock产生的Condition的锁来阻塞自身,等到该组线程都执行了则对该组进行全部唤醒;
(3)当子线程发生中断抛出异常,会直接唤醒当组被阻塞的所有线程并重置下组的初始条件,不会导致程序异常。
CyclicBarrier简单使用
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
for (int i = 0; i < 10; i++){
int num = i;
new Thread(()->{
System.out.println("线程"+num+"初始化!");
try {
cyclicBarrier.await();
System.out.println("------------------线程"+num+"被唤醒执行!-----------------");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("main 线程执行!");
}
(四)CyclicBarrier源码分析
(1)核心成员变量
//使用ReentrantLock锁初始化锁,便于condition产生每组条件
private final ReentrantLock lock = new ReentrantLock();
//循环锁的核心条件,依赖于ReentrantLock锁
private final Condition trip = lock.newCondition();
//初始屏障数
private final int parties;
//额外的线程任务如每个线程都要执行共同的任务时使用
private final Runnable barrierCommand;
/**
* 主要是作为辅助标志
* 是否异常中断该组阻塞(BrokenBarrierException)和重置下组条件(调用breakBarrier方法)
*/
private Generation generation = new Generation();
//该类为辅助内部类
private static class Generation {
boolean broken = false;
}
(2)构造函数
/**
* CyclicBarrier设置屏障数,采用默认的barrierAction为null,不执行额外步骤去初始化屏障
*/
public CyclicBarrier(int parties) {
this(parties, null);//调用自身的另一个自定义化的构造函数
}
/**
* CyclicBarrier屏障数、额外步骤内容参数去初始化屏障
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;//设置初始化屏障数
this.count = parties;//剩余屏障数
this.barrierCommand = barrierAction;//额外步骤内容
}
(3)await方法(核心)
//暴露给外部用于阻塞调用的API
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);//调用自身内部的具体阻塞方法(核心)
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
dowait()作用:
每个线程执行dowait方法时,利用ReentrantLock去生成Condition,基于该条件下去阻塞线程,当前这组每次count自减,当count等于0则代表当前条件下阻塞的线程进行唤醒,并重置下一组的开始条件count。
/**
*dowait主要是为CyclicBarrier普通等待和超时等待await服务
*timed代表区分普通等待(false)和超时等待(true)、nanos代表超时时间
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException,BrokenBarrierException,TimeoutException {
final ReentrantLock lock = this.lock;//内部锁,利用ReentrantLock进行初始化
lock.lock();//获取锁
try {
final Generation g = generation;//获取屏障辅助,默认其broken为false
if (g.broken)//是否需要被意外打破
throw new BrokenBarrierException();
if (Thread.interrupted()) {//当前线程是否有中断标记,该组每个线程都会进行判断
breakBarrier();//重置count和唤醒当前组所有被阻塞的线程
throw new InterruptedException();
}
int index = --count;//当前剩余达到屏障值
if (index == 0) { //是否到达改组的唤醒值
boolean ranAction = false;//是否重置下组初始条件
try {
final Runnable command = barrierCommand;//构造方法中指定是否执行补充任务
if (command != null)
command.run();//执行补充任务
ranAction = true;//防止程序出现异常,导致执行finally中逻辑
nextGeneration();//正常唤醒当前组阻塞线程、重置下组初始值count
return 0;
} finally {
if (!ranAction)
breakBarrier();//程序异常,需要唤醒当前组被阻塞线程和初始下组条件
}
}
//下方为自旋,主要是未达到该组的屏障值进行阻塞
for (;;) {
try {
if (!timed)//普通阻塞
trip.await();//调用基于ReentrantLock中产生的Condition条件中的await方法进行阻塞
else if (nanos > 0L)//超时阻塞
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {//try逻辑出现中断异常
if (g == generation && ! g.broken) {//异常辅助是否被改变过
breakBarrier();//上述都是默认值时满足if条件进行唤醒和重置
throw ie;
} else {
//说明不满足if则代表执行该代码的线程是被中断
Thread.currentThread().interrupt();
}
}
if (g.broken)//检查改组中是否有被改变为true
throw new BrokenBarrierException();
if (g != generation)//检查是否为该组的辅助标志
return index;//返回上组的剩余屏障数
if (timed && nanos <= 0L) {//超时阻塞await时且已超时执行
breakBarrier();//唤醒当前组阻塞线程和重置下组条件
throw new TimeoutException();
}
}
} finally {
lock.unlock();//释放当前线程锁对象执行权
}
}
(4)辅助方法
/**
* 改变broken初始false值为true,便于其他地方抛出异常如dowait中有判断g.broken是否抛出异常
* 重置下组的count值
* 唤醒当前组被阻塞的线程
*/
private void breakBarrier() {
generation.broken = true;//改变该组异常抛出标志
count = parties;//重置下一组的初始值
trip.signalAll();//唤醒当前组的阻塞线程
}
/**
* 正常唤醒当前组被阻塞的线程、重置下组初始条件count和Generation中的broken值
*/
private void nextGeneration() {
trip.signalAll();//正常唤醒当前组被阻塞的线程
count = parties;//重置下组初始条件
generation = new Generation();//初始化Generation中的broken值
}