【并发编程】CountDownLatch与CyclicBarrier

 CountDownLatch

简介

CountDownLatch,意思是倒数门闩。它的作用是多个线程做汇聚。主线程开启了 A、B、C 三个线程做不同的事情,但是主线程需要等待 A、B、C 三个线程全部完成后才能继续后面的步骤。此时就需要 CountDownLatch 出马了。CountDownLatch 会阻塞主线程,直到计数走到 0,门闩才会打开,主线程继续执行。而计数递减是每个线程自己操作 CountDownLatch 对象实现的。

这种场景很常见: 篮球比赛中,作为控球后卫,如果没有快攻机会,那就需要等到中锋、大前锋、小前锋、得分后卫都跑到位了,我才能决定怎么组织进攻。又比如我们报团去旅游,必须所有人都到机场了,才能一起出发

对于我们的程序来说这种场景也挺多的,比如你的订单信息可能需要从多个微服务取得数据,汇总后加工才返回给前台。此时从多个微服务取得数据可以是多个子线程来完成。

使用

public static void main(String[] args) throws InterruptedException {

    System.out.println("控球后卫到位!等待所有位置球员到位!");

    new Thread(()->{
        System.out.println("得分后卫到位!");
    }).start();

    new Thread(()->{
        System.out.println("中锋到位!");
    }).start();

    new Thread(()->{
        System.out.println("大前锋到位!");
    }).start();

    new Thread(()->{
        System.out.println("小前锋到位!");
    }).start();

    System.out.println("全部到位,开始进攻!");
}

这样的运行结果是 

没有使用门栓,就无法控制所有线程的阻塞。

接下来看一下有门栓的代码

public class Client {
    private static  final CountDownLatch countDownLatch = new CountDownLatch(5);
    public static void main(String[] args) throws InterruptedException {

        System.out.println("控球后卫到位!等待所有位置球员到位!");
        countDownLatch.countDown();

        new Thread(()->{
            System.out.println("得分后卫到位!");
            countDownLatch.countDown();
        }).start();

        new Thread(()->{
            System.out.println("中锋到位!");
            countDownLatch.countDown();
        }).start();

        new Thread(()->{
            System.out.println("大前锋到位!");
            countDownLatch.countDown();
        }).start();

        new Thread(()->{
            System.out.println("小前锋到位!");
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();

        System.out.print("全部到位,开始进攻!");
    }
}

运行结果如下:

然后我们可以看到,初始的子线程为5,计算器初始也是5,在进攻前设置门栓,也就是countDownLatch.await()。当一个子线程完成是执行countDownLatch.countDown(),计算器减一。必须所有的子线程有执行完毕,计算器为0以后,我们就打开门栓,执行门栓后边的代码。

CyclicBarrier

简介

CountDownLatch让多个线程完成后,再促使主线程继续向下执行。不过它有一定的局限性,无法被重复使用。CyclicBarrier 不会有这个问题。

CyclicBarrier 从字面上理解为循环栅栏。栅栏自然起到的就是屏障的作用,阻止线程通过,而循环则是指其可以反复使用。

使用

出租车司机招揽坐车客人,满四人即可发车,司机每天都可以做很多这样的生意。我们来模拟一下场景

public class Client {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () ->
                System.out.println("人满了发车")
        );
        //模拟十个乘客
        IntStream.range(1, 11).forEach(number -> {
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            new Thread(() -> {
                try {
                    System.out.println("第 " + number + " 乘客上车了!");
                    cyclicBarrier.await();
                    System.out.println("第 " + number + " 乘客出发了!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        });
    }

首先初始化是计数器为4,并且计数器为零后,打开栅栏,会打印人满了发车。然后是每一个乘客上车都是一个线程,上车后调用 cyclicBarrier.await() 。这里就是屏障点,此时当前线程会阻塞在此处,并且计数器被减 1,每当四个乘客完成上车操作,cyclicBarrier 就会触发 “人满了发车” 的操作。而最后两位乘客上车后,由于没有新的乘客上车,计数器不会被减到 0,导致无法越过屏障,所以永远不会发车。

 

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读