一. CyclicBarrier简介
1:CyclicBarrier(可重用屏障/栅栏) 类似于 CountDownLatch(倒计数闭锁),它能阻塞一组线程直到某个事件的发生。
2:与闭锁的关键区别在于,所有的线程必须同时到达屏障位置,才能继续执行。
3:闭锁用于等待事件,而屏障用于等待其他线程。
4:CyclicBarrier 可以使一定数量的线程反复地在屏障位置处汇集。当线程到达屏障位置时将调用 await() 方法,这个方法将阻塞直到所有线程都到达屏障位置。如果所有线程都到达屏障位置,那么屏障将打开,此时所有的线程都将被释放,而屏障将被重置以便下次使用。
5:CyclicBarrier 是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
6:在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用 CyclicBarrier 很有帮助。
7:这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的。
二:CyclicBarrier 的应用场景
CyclicBarrier 常用于多线程分组计算。
比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候就可以选择CyclicBarrier。
三:常用API方法总结:
1:CyclicBarrier(parties) 方法
初始化相互等待的线程数量的构造方法。
2:CyclicBarrier(parties,Runnable barrierAction) 方法
初始化相互等待的线程数量以及屏障线程的构造方法。
屏障线程的运行时机:等待的线程数量 =parties 之后,CyclicBarrier 打开屏障之前。
例如在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。
3:getParties() 方法
获取 CyclicBarrier 打开屏障的线程数量。
4:getNumberWaiting() 方法
获取正在 CyclicBarrier 上等待的线程数量。
5:await() 方法
在 CyclicBarrier 上进行阻塞等待,直到发生以下情形之一。
在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行。
当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行。
其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
线程调用 await() 表示自己已经到达栅栏。
BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程await() 时被中断或者超时。
6:await(timeout,TimeUnit) 方法
在 CyclicBarrier 上进行限时的阻塞等待,直到发生以下情形之一。
在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行。
当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行。
当前线程等待超时,则抛出 TimeoutException 异常,并停止等待,继续执行。
其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行
7:isBroken() 方法
获取是否破损标志位 broken 的值,此值有以下几种情况。
CyclicBarrier 初始化时,broken=false,表示屏障未破损。
如果正在等待的线程被中断,则 broken=true,表示屏障破损。
如果正在等待的线程超时,则 broken=true,表示屏障破损。
如果有线程调用 CyclicBarrier.reset() 方法,则 broken=false,表示屏障回到未破损状态。
8:reset() 方法
使 CyclicBarrier 回归初始状态,它做了两件事。
如果有正在等待的线程,则会抛出 BrokenBarrierException 异常,且这些线程停止等待,继续执行。
将是否破损标志位 broken 置为 false。
四:CyclicBarrier 和 CountDownLatch 的区别
CountDownLatch 是一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行;CyclicBarrie是N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
CountDownLatch 的计数器只能使用一次。而 CyclicBarrier的计数器可以使用 reset()方法重置;CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
CountDownLatch 采用减计数方式;CyclicBarrier 采用加计数方式。
五:使用 CyclicBarrier 的注意事项
CyclicBarrier 使用独占锁来执行 await() 方法,并发性可能不是很高。
如果在等待过程中,线程被中断了,就抛出异常。
但如果中断的线程所对应的 CyclicBarrier 不是这一代,比如在最后一次线程执行 signalAll 后,并且更新了这个 " 代 " 对象。在这个区间,这个线程被中断了,那么, JDK 认为任务已经完成,不必在乎中断,就只打了一个中断 interrupt() 标记。
如果线程被其他的 CyclicBarrier 唤醒,那么 g 肯定等于 generation,这个事件就不能 return 了,而是继续循环阻塞。
反之,如果是当前 CyclicBarrier 唤醒,就返回线程在 CyclicBarrier 的下标,表示完成了一次冲过屏障的过程。
CyclicBarrier 的 await() 方法是使用 ReentrantLock 和 Condition 控制实现的。
当调用 CyclicBarrier 的 await() 方法会间接调用 ConditionObject 的 await() 方法,会向 Condition 的等待队列中加入元素,当屏障关闭后首先执行指定的barrierAction(),然后依次执行等待队列中的任务,有先后顺序。
CyclicBarrier 类中加锁的方法有 dowait(),isBroken(),reset(),getNumberWaiting()。
六: 总结
CyclicBarrier 的用途是让一组线程互相等待,直到全部到达某个公共屏障点才开始继续工作。
CyclicBarrier 是可以重复利用的。
在等待的只要有一个线程发生中断,则其它线程就会被唤醒继续正常运行。
CyclicBarrier 指定的任务是进行 barrier 处最后一个线程来调用的,如果在执行这个任务发生异常时,则会传播到此线程,其它线程不受影响继续正常运行。