上一篇我们讲了CountDownLatch可以解决多线程同步的问题。与join相比,它的应用范围更广。它不仅可以应用于线程,还可以应用于线程池。但是,CountDownLatch是一次性计数器。对于农药之王来说,在团战中我们是无法分出胜负的,所以在某些场景下,我们需要复用某个等待函数。这就是我们今天所做的。另一个要介绍的主角——CyclicBarrier。CyclicBarrierCyclicBarrier翻译成中文是循环栅栏(Barrier)的意思,其大致意思是实现一个可回收的屏障。CyclicBarrier的作用是让一组线程互相等待。当到达一个公共点时,所有之前等待的线程将继续执行,CyclicBarrier函数可以被重用。比如雷哥想坐班车回老家,因为途中不允许乘客上下车,所以运营公司为了利润最大化,会等到车满员。这种人满了就开火车的场景,正是CyclicBarrier擅长的,因为它可以重复使用(不像CountDownLatch只能使用一次)。CyclicBarrierVSCountDownLatchCountDownLatch:一个或多个线程等待其他N个线程完成某事后才能执行。CountDownLatch就像玩杀虫之王开局的加载,每个人都得等其他人加载到100%后才能开始游戏。CyclicBrrier:N个线程互相等待,直到有足够数量的线程到达屏障点,之前等待的线程才能继续执行。CyclicBrrier就像一个老司机开车。如果车上有空座,大家就得等老司机坐满了再发动车子。CyclicBarrier使用importjava.util.Date;importjava.util.Random;importjava.util.concurrent.*;publicclassCyclicBarrierExample{publicstaticvoidmain(String[]args){//创建CyclicBarrierfi??nalCyclicBarriercyclicBarrier=newCyclicBarrier(2,newRunnable(){@Overridepublicvoidrun()System.out.println("已满,准备开始:"+newDate());}});//线程调用的任务Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){//生成随机数1-3intradomNumber=newRandom().nextInt(3)+1;//进入任务System.out.println(String.format("我是:%s,走:%d秒后到站,现在time:%s",Thread.currentThread().getName(),randomNumber,newDate()));try{//模拟执行TimeUnit.SECONDS.sleep(randomNumber);//调用CyclicBarriercyclicBarrier.await();//任务执行System.out。println(String.format("Thread:%s登机,时间:%s",Thread.currentThread().getName(),newDate()));}catch(InterruptedExceptione){e.printStackTrace();}catch(BrokenBarrierExceptione){e.printStackTrace();}}};//创建线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(10);//执行任务1threadPool.submit(runnable);//执行任务2threadPool.submit(runnable);//执行任务3threadPool.submit(runnable);//执行任务4threadPool.submit(runnable);//等待所有任务执行完毕,终止线程poolthreadPool.shutdown();}}上面代码的执行结果如下:从上面的结果可以看出,当CyclicBarrier的计数器设置为2时,老司机会发送第一波小车在线程2和线程3都到达barrierpoint之后,然后2s之后,thread1和thread4也同时进入了barrierpoint。这个时候老司机可以再派一波车。实现原理先来看一下CyclicBarrier的类图:从上图可以看出,CyclicBarrier是基于排他锁ReentrantLock实现的,其底层也是基于AQS实现的。CyclicBarrier类中有一个计数器计数。当count不为0时,每个线程在到达barrierpoint时都会调用await方法阻塞自己。此时计数器会减1,直到计数器减为0。被调用await方法阻塞的线程会被唤醒继续执行。当计数计数器变为0时,进入下一轮阻塞。这时parties(parties是newCyclicBarrier(parties)时设置的值)会把它的值赋值给count,实现复用。常用方法CyclicBarrier(parties):构造函数,初始化等待对方的线程数。CyclicBarrier(parties,RunnablebarrierAction):初始化相互等待的线程数和barrier线程的构造函数。当CyclicBarrier的计数器变为0时,将执行barrierAction构造函数。getParties():获取CyclicBarrier开启屏障的线程数,也称为parties数。getNumberWaiting():获取在CyclicBarrier上等待的线程数。await():在CyclicBarrier上阻塞等待,直到出现以下情况之一:当在CyclicBarrier上等待的线程数达到parties时,所有线程将被释放并继续执行;如果当前线程被中断,则抛出InterruptedException,程序停止Wait,继续执行;如果其他等待线程被中断,当前线程将抛出BrokenBarrierException,停止等待,继续执行;如果其他等待线程超时,当前线程将抛出BrokenBarrierException,停止等待,继续执行;其他线程调用CyclicBarrier.reset()方法,当前线程抛出BrokenBarrierException,停止等待,继续执行。await(timeout,TimeUnit):在CyclicBarrier上等待有限的时间,直到出现以下情况之一:当在CyclicBarrier上等待的线程数达到parties时,所有线程将被释放并继续执行;如果当前线程被中断,则抛出InterruptedException,停止等待,继续执行;当前线程会抛出TimeoutException,停止等待,继续执行;如果其他等待线程被中断,当前线程将抛出BrokenBarrierException,停止等待,继续执行;当其他等待线程超时,当前线程抛出BrokenBarrierException,停止等待,继续执行;当其他线程调用CyclicBarrier.reset()方法时,当前线程抛出BrokenBarrierException,停止等待,继续执行。isBroken():获取breaking标志位broken的值,有如下条件:当CyclicBarrier初始化时,broken=false,表示barrier没有被break;如果等待线程被中断,则broken=true,表示屏障被打破;如果等待线程超时,则broken=true,表示屏障被打破;如果线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障返回到未中断状态。reset():将CyclicBarrier返回到其初始状态。直观上,它做了两件事:如果有等待的线程,将抛出BrokenBarrierException,这些线程将停止等待并继续执行。将broken标志位broken设置为false。总结CyclicBrrier通过独占锁ReentrantLock实现计数器的原子更新。CyclicBrrier最常用的方法是await()方法。使用该方法会将计数器设置为-1,并判断当前计数器是否为0,如果不为0,则阻塞等待,直到定时器为0,再继续执行剩余的任务。与CountDownLatch相比,CyclicBrrier的优点是可以重复使用。参考&致谢blog.csdn.net/qq_39241239/article/details/87030142blog.csdn.net/zzg1229059735/article/details/61191679www.cnblogs.com/yaochunhui/p/13494689.html
