java-CountDownLatch vs CyclicBarrier

CountDownLatch 和 CyclicBarrier对比

两者都能起到计数器的作用;

不同点在于

  1. 构造方法,对于两者的构造方法都支持输入计数器的初始化值,而CyclicBarrier则还支持设置当计数器归零的回调事件
  2. await方法,对于CountDownLatch一般是主线程使用阻塞等待所有的异步线程执行结束;而CyclicBarrier则是在异步线程内使用不光起到阻塞的作用还起到计数器减一的作用;对于CyclicBarrier而言所有的异步线程都阻塞到await,直到计数器归零所有被阻塞的线程才会被唤醒继续往后执行; 
  3. 对于CyclicBarrier中await还有一点不同在于,当计数器归零后,首先会执行回调方法,并开启下一个generation;
  4. 对于CyclicBarrier由于存在reset支持重复利用,因此存在一个generation来进行协调;
    1. 如果在当前同一generation中其中一个线程出现线程中断情况时,(breakBarrier)首先会标记当前generation已损坏,并唤醒所有被阻塞的线程,当有新的线程进来时,会直接抛出当前generation的CyclicBarrier不可使用异常
    2. 而当计数器归零时,则会(nextGeneration)开启一个新的generation
    3. 而对于reset操作则是会将以上两个操作结合在一起,先break当前generation然后再开启新的generation
  5. 由于CyclicBarrier在归零后会直接开启下一个generation,且会重置计数器,因此当实际await线程数量超过计数器的倍数时,超过的部分线程会进入持续阻塞状态,除非有另外的线程重置当前CyclicBarrier
  6. 对于CountDownLatch提供了获取剩余计数器的api而CyclicBarrier则并无法得知剩余计数器数量

  

 public static void main(String[] args) {
        int count = 5;
        ExecutorService executorService = new ThreadPoolExecutor(1, 10, 1L, TimeUnit.MINUTES, new SynchronousQueue<>(), new ThreadFactoryBuilder().setNameFormat("cyclic_barrier_demo-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());
        Random random = new Random();
        // 将一个二维数组转换为一维数组
        int[][] rows = new int[count][count];
        int[] mergeRows = new int[2 * count];
        CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> {
            // 可以起到方法回调的作用
            for (int j = 0; j < count; j++) {
                int[] firstRows = rows[j];
                int length = firstRows.length;
                for (int i = 0; i < length; i++) {
                    mergeRows[i + j] = firstRows[i];
                }
            }
            log.info("当前计数器已归零");
        });
        for (int i = 0; i < 6; i++) {
            int finalI = i;
            // 对于第6个线程
            // 如果线程池中的最大线程数量小于计数器时,且拒绝策略为调用主线程时,会导致主线程被阻塞在await处,导致当前for循环不会继续执行,从而导致程序假死
            executorService.submit(() -> {
                log.info("线程:{}进入", Thread.currentThread().getName());
                for (int j = 0; j < count; j++) {
                    if (finalI < count) {
                        rows[finalI][j] = random.nextInt(10);
                    }
                }
                try {
                    // 当执行当前操作的线程数量不为计数器最大值的倍数时,则会存在异步线程被一直阻塞的情况,除非外部重置当前CyclicBarrier
                    cyclicBarrier.await();// 计数器不为0时,当线程执行到此处时,当前线程会被阻塞; 当计数器归为0时,会首先回调CyclicBarrier中的runnable,并唤醒当前所有被阻塞的线程
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                // 只有当计数器为0时,所有的异步线程才会执行到当前代码
                log.info("线程:{}执行结束", Thread.currentThread().getName());
            });
        }
        executorService.shutdown();
        Collections.singletonList(mergeRows).forEach(v -> {
            log.info("{}", v);
        });


        /**
         * 对比countDownLatch 和 cyclicBarrier
         * 1:都拥有计数器的功能
         * 2:对于CountDownLatch的主要功能一般是,异步线程执行,主线程阻塞; 而cyclicBarrier是异步线程阻塞,主线程执行(保证线程池的拒绝策略不是调用主线程,否则会导致主线程也被阻塞),且存在回调事件;
         * 3:CountDownLatch的计数器不支持重置,而cyclicBarrier支持计数器重置
         * 4:CountDownLatch可以获取到当前剩余计数,而cyclicBarrier不支持
         */
    }

 

    public static void main(String[] args) throws InterruptedException {
        // 创建一个数量为5的计数器
        CountDownLatch countDownLatch = new CountDownLatch(5);
        ExecutorService executorService = new ThreadPoolExecutor(1, 3, 1L, TimeUnit.MINUTES, new SynchronousQueue<>(), new ThreadFactoryBuilder().setNameFormat("count_down_lath_demo-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());
        // 当实际执行次数小于计数器时,由于计数器一直不会等于0,因此当前线程在执行到" countDownLatch.await();" 会被阻塞
//        task(4, countDownLatch, executorService);
        // 当实际执行次数等于计数器时,当前代码会执行结束
//        task(5, countDownLatch, executorService);
        // 当实际执行次数大于计数器时,当前代码会执行结束,当countDownLath计数器归零时,实际countDownLath已没有任何效果
        task(6,countDownLatch,executorService);
        new Thread(()->{
           while (true){
               long count = countDownLatch.getCount();
               log.info("当前计数器为:{}",count);
               if(count==0){
                   break; // 由于进程中当前子线程未结束,所以当前整个进程也不会退出
               }
               try {
                   Thread.sleep(1000L);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }).start();
        countDownLatch.await();// 当计数器大于0时,当前线程会被阻塞在此,原因在于,在调用java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node, int, boolean, boolean, boolean, long)时由于计数器不归零,导致当前方法一直不会返回,因此才会阻塞当前线程
        executorService.shutdown();
        log.info("主线程结束");
    }

    public static void task(int count, CountDownLatch countDownLatch, ExecutorService executorService) {
        for (int i = 0; i < count; i++) {
            executorService.submit(() -> {
                log.info("线程:{}执行", Thread.currentThread().getName());
                // 计数器减1,当计数器为0时,实际内部代码并不会完整的执行,并不会再次修改计数器,因此计数器最小为0
                countDownLatch.countDown();
            });
        }
    }

 

    

      

posted @ 2020-09-28 21:11  郭星  阅读(193)  评论(0)    收藏  举报