一, CyclicBarrier 能做什么事情
和 CountDownLatch 一样, CyclicBarrier 也是 java.util.concurrent 包下的一个类; 从类名就可以看出, 这是一个可以循环使用 (Cylcic) 的屏障 (Barrier), 所做的事情就是让一组线程到达一个屏障(同步点) 时被阻塞, 直到这组线程中的最后一个到达屏障时, 屏障才会打开, 之前阻塞的线程继续运行. 过程如下图所示
上图中的三个线程中各有一个 barrier.await, 任何一个线程在运行到 barrier.await 时都会进入阻塞等待状态, 直到三个线程都到了 barrier.await 时才从 await 返回, 继续向后运行.
二, CyclicBarrier 如何使用
实例化 CyclicBarrier 对象时通过它的构造函数设置屏障要拦截的线程 (调用 barrier.await 的次数) 的数据量, 每个线程通过调用 CyclicBarrier 实例的 await 方法告诉 CyclicBarrier 我已经到达屏障, 并将自己阻塞.
此外, 如果在构造 CyclicBarrier 时设置了一个 Runnable 实现, 那么最后一个 barrier.await 的线程会执行这个方法, 以完成一些预设工作
CyclicBarrier 经常用于多线程计算数据, 最后要将计算结果合并的场景. 例如一个 Excel 表记录了用户一个季度所有的银行流水, 每个 sheet 记录了该用户每个月的银行流水情况, 要统计该用户整个季度的银行流水状况时, 可以先使用多线程统计每个 sheet 的银行流水, 都执行完毕后, 使用每个线程的计算结果来计算出该用户整个季度的银行流水状况.
- public class CyclicBarrierTest implements Runnable{
- /* 创建一个 CyclicBarrier 实例, 屏障数据设为 3, 处理完之后执行当前类的 run 方法 */
- private CyclicBarrier cb = new CyclicBarrier(3,this);
- /* 创建线程池, 只有三个月的数据, 所以只需三个线程 */
- private Executor executor = Executors.newFixedThreadPool(3);
- /* 创建一个 ConcurrentHashMap, 用来保存每个 sheet 计算出的结果 */
- private ConcurrentHashMap<String,Integer> sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();
- public void count() {
- for(int i = 0;i<3;i++){
- /* 每个线程用来处理单个 sheet 中的任务 */
- executor.execute(new Runnable() {
- public void run() {
- /* 此处加入复杂的逻辑处理代码 */
- sheetBankWaterCount.put(Thread.currentThread().getName(),1);
- try {
- /* 线程完成工作后调用 await 设置屏障 */
- cb.await();
- }catch (BrokenBarrierException e){
- e.printStackTrace();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- });
- }
- }
- /* 等到所有的 */
- public void run() {
- int res = 0;
- /* 根据之前多线程的结果计算出整个季度的银行流水 */
- for (Map.Entry<String,Integer> sheet: sheetBankWaterCount.entrySet()) {
- res += sheet.getValue();
- }
- sheetBankWaterCount.put("result",res);
- /* 将结果输出 */
- System.out.println(res);
- }
- public static void main(String[] args){
- CyclicBarrierTest test = new CyclicBarrierTest();
- /* 注意, 此时不需要调用 test.run(), 最后一个 await 方法会调用 run 方法 */
- test.count();
- }
- }
三, 使用 CyclicBarrier 时要注意的问题
在线程池中使用 CyclicBarrier 时一定要注意线程的数量要多于 CyclicBarrier 实例中设置的阻塞线程的数量就会发生死锁. 调用 await()方法的次数一定要等于屏障中设置的阻塞线程的数量, 否则也会死锁.
四, CyclicBarrier 和 CountDownLatch 的区别
首先二者都能让一个或多个线程阻塞等待, 都可以用在多个线程间的协调, 起到线程同步的作用. 但 CountDownLatch 是多个线程都进行了 countDown 之后才会触发时间, 唤醒 await 在 latch 上的线程, 执行完 countDown 操作之后会继续自己线程的工作. 而 CyclicBarrier 是一个栅栏, 用于同步所有调用 await 方法的线程, 等到所有的方法都执行了 await 方法后, 所有的线程才会返回各自执行自己的工作.
CountDownLatch 计数器只能使用一次, 而 CyclicBarrier 的计数器可以调用 reset() 方法重置, 能处理更加复杂的业务场景.
java 并发之 CountDownLatch 使用方法
来源: https://juejin.im/post/5b3b226a6fb9a04fb309d6de