一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别_并行执行


一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别_基本操作_02

春时享受花红草绿,冬时欣赏冰雪风霜

晴天时爱晴,雨天时爱雨


一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别_并行执行_03


日常工作中,经常会碰到这样的场景:有时候数据量特别大,任务量特别多,我们通常会开启多线程去分批执行任务,在所有任务执行完了之后,再去执行接下来的作业。


这时候,会想到 java 并发包提供的基础工具类,其中 有 CountDownLatch 和 CyclicBarrier ,它们都是提供多线程环境的协调功能,但是具体有什么区别呢?


一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别_多线程_04


CountDownLatch 操作的是事件,阻塞足够多的次数即可,不管几个线程;而 CyclicBarrier 侧重点是线程,强调多个线程间互相等待,同时结束。



01

CountDownLatch 用法


从代码层面,CountDownLatch 的用法是:


// 设置10个计数CountDownLatch countDownLatch = new CountDownLatch(10);// 每次调用即可减1 countDownLatch.countDown();    // 其他线程一直等待减到0后,才继续执行countDownLatch.await()


从一个场景出发:


现在有订单库和派送单库,先查询订单,再查询派送单,之后对比订单和派送单,将差异写入差异库。


其中查询订单库和查询派送单库,可以用两个线程并行执行。都执行完了之后,才执行对账。


一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别_多线程_05


我们可以写一下伪代码:


Executor executor = Executors.newFixedThreadPool(2);while(存在未对账订单) {    // 计数器初始化为2    CountDownLatch latch = new CountDownLatch(2);    // 查询未对账订单    executor.execute(() -> {        pOrders = getPOrders();        latch.countDown();    });        // 查询派送单    executor.execute(()-> {        dOrders = getDOrders();        latch.countDown();    });        // 等待两个查询结束    latch.await();        // 执行对账操作    diff = check(pOrders,dOrders);        // 差异写入差异库    save(diff);}


我们使用只有2个核心线程的线程池,分别执行查询订单和派送单的操作,并且初始化了一个大小为 2 的 CountDownLatch,每次查询完后,都要 countDown();

主线程则一直等待减为 0 了之后,才开始继续往下执行。



02

CyclicBarrier 用法


从代码使用角度来说:


// 初始化值为5的栅栏CyclicBarrier cyclicBarrier = new CyclicBarrier(5);// 每个线程调用 await()cyclicBarrier.await();// 等到有 5 个线程都执行了 await() 之后,继续执行。// 并且 栅栏的 计数器会自动重置为 5 ,可以接着用


然后我们模拟一个场景


在英雄联盟中,选好英雄之后,会等待所有 10 个玩家进度条都到 100% 才开始游戏,我们可以使用 CyclicBarrier 来模拟这个场景


public class CyclicBarrierTest {    private final static ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(5);    private final static CyclicBarrier BARRIER = new CyclicBarrier(10);    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            final String name = "玩家" + i;            EXECUTOR_SERVICE.execute(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(2000);                        System.out.println(name + "已准备,等待其他玩家准备...");                        BARRIER.await();                        Thread.sleep(1000);                        System.out.println(name + "已加入游戏");                    } catch (InterruptedException e) {                        System.out.println(name + "离开游戏");                    } catch (BrokenBarrierException e) {                        System.out.println(name + "离开游戏");                    }                }            });        }        EXECUTOR_SERVICE.shutdown();    }}


最后,比较一下 CountDownLatch 和 CyclicBarrier 的不同点:


  • CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这个限制,可以重用;


  • CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown 足够多的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。


  • CyclicBarrier 的基本操作组合,则就是 await,当所有伙伴 (parties)都调用了 await,才会继续进行任务,并自动进行重置。


  • 注意,正常情况下,CyclicBarrier 的重置都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出 BrokenBarrierException 异常。

  • CyclicBarrier 侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。



下次见 -.-