CyclicBarrier的使用
一、含义:可循环屏障,意思可以多次使用,并且可以阻塞当前线程
二、使用场景:一般多线程下使用
三、使用方法:
//只考虑正常情况下,不考虑线程中断、或者有异常产生...
//1.实例化对象,parties为屏障放行的条件
CyclicBarrier barrier=new CyclicBarrier(parties);
//2.让当前线程阻塞,每一次await,parties就会减一,直到为0时,就会放行
//3.放行之后,会将parties恢复原来的值,也就是可以再次使用
barrier.await();
四、一般用途
//1.可以用来等待所有线程初始化后,再一起执行
for(int i=0;i<THREAD_NUM;i++){
final int num=i;
new Thread(()->{
try {
barrier.await();
System.out.println(num+":大家到齐了,开始执行了");
......
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
//2.可以用来等待所有线程结束
for(int i=0;i<THREAD_NUM;i++){
final int num=i;
new Thread(()->{
try {
......
//等到0时,就放行,斌且重置parties
barrier.await();
System.out.println(num+":大家到齐了,可以关闭了");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
五、源码分析
//1.构造方法,有俩个,最终都执行这个构造方法
//parties放行条件,barrierAction含义是路障(可以终止下次的循环,指的是下次的await)
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
//2.await方法,最终指向这个方法,timed超时标记,nanos为超时时间
private int dowait(boolean timed, long nanos)
//正常放行,关键代码,每一个线程进入都会进行加锁,并且接下来会--count,count的初始值就是parties
//最终index为0时就是正常放行,这里自然会有疑问,尽然加锁那么index怎么可能会为0(不考虑parties为1),所以自然要去寻找释放锁的位置!!!
lock.lock();
.....
int index = --count;
//3.Lock锁的释放,未设置超时,直接释放锁,进入等待池;反之,等待一段时间在释放锁,进入等待池,这样子其他线程就有机会进入,那么index就有可能为0了,这样就可以进行放行了
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
//4.放行,一般情况下释放都是执行nextGeneration()来释放,主要就是给全局的generation重新赋值
if (index == 0) { // tripped
//用于finally里的判断
boolean ranAction = false;
try {
//路障,默认情况下是null,所以这个run不会执行
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//唤醒所有等待池的线程,并且重置count,以及generation(其他线程退出循环的其中一个条件)!!!
nextGeneration();//可以点进去看下,大概意思就这样,只有三行
return 0;
} finally {
if (!ranAction)
//唤醒所有等待池的线程,并且重置count,以及设置generation.broken=true就是中断标志(其他线程退出循环的其中一个条件)!!!
breakBarrier();//可以点进去看下,大概意思就这样,只有三行
}
}
//其他线程如何知道是否可以放行(正常放行)
//index==0的那个线程执行了nextGeneration(),将全局的generation变量重新引用了一个值,所以这里的判断会为true,那么就可以退出循环了
//至此所有线程就通过这个方式退出,就可以执行其他操作了
if (g != generation)
return index;
//5.到这里为止,一直强调的正常放行,接下来看下其他放行方法(线程中断、generation.broken...)
//看下以下代码,breakBarrier()注意这个方法
if (Thread.interrupted()) {
breakBarrier();//!!!
throw new InterruptedException();
}
if (!ranAction)
breakBarrier();//!!!
if (g == generation && ! g.broken) {
breakBarrier();//!!!
throw ie;
}
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
//很明显所有的信息都指向这个方法,这个方法的代码
private void breakBarrier() {
//更改这个broken标记,关键就在这个,这样其他线程可以通过判断退出
generation.broken = true;
//重置count
count = parties;
//唤醒所有线程
trip.signalAll();
}
//线程退出的另一个判断,g就是generation,这就是breakBarrier()方法退出循环的方式,重新标记一下即可
if (g.broken)
throw new BrokenBarrierException();
//既然知道了broken这个标记,那么接下来就是如何触发breakBarrier()这个方法了
//6.触发breakBarrier()方法
//index==0,放行代码里,这个代码的finally里面有一个breakBarrier(),而只有ranAction ==false时,if判断才可以通过,ranAction 的初始值也刚好是false
//但有个问题,就是里面有ranAction = true这个操作,并且barrierCommand默认为null,那么ranAction = true这个操作必然会被执行,这样的话finally里的方法就必然不会被执行
//那么想要ranAction = true;不被执行,就只有从barrierCommand(路障)入手了,因为只有command.run();这个出现问题(抛出异常),导致后面ranAction = true;不执行,那么finally就可以执行breakBarrier()
//barrierCommand其实就是构造方法的第二个参数,所以只要使用这个构造方法,使得在某些条件下抛出一个异常,那么ranAction = true;就不会执行了,那么breakBarrier()就会被执行了
if (index == 0) { // tripped
boolean ranAction = false;
try {
//默认为null
final Runnable command = barrierCommand;
if (command != null)
command.run();
//问题!!!
ranAction = true;
nextGeneration();
return 0;
} finally {
//只有 ranAction = true;不被执行才可以;也就是command.run();要抛出异常来中断下面的操作
if (!ranAction)
breakBarrier();
}
}
//调用线程中断方法也可以,Thread.interrupt(),这个仅仅是让线程标记为中断,而不是直接就中断,所以标记完后,下次在进入时,if判断通过后,也可以执行breakBarrier();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//设置超时也可以执行breakBarrier();
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
六、源码分析总结
1.退出循环有正常退出、broken标记退出、超时退出、线程中断退出
2.正常退出,index==0即可
broken标记退出,执行了breakBarrier()方法即可
超时退出,设置了超时即可
线程中断退出,设置了线程中断即可