(Java 高并发程序设计) 一篇文章带你深入了解多线程中的倒计数器(CountDownLatch)和循环栅栏(CyclicBarrier)

一、倒计数器:CountDownLatch

CountDownLatch 是一个非常实用的多线程控制工具类。“Count Down” 在英文中意为倒计数,Latch 意为门闩的意思。如果翻译成为倒计数门闩,我想大家都会不知所云吧!因此,这里简单地称之为倒计数器。在这里,门闩的含义是把门锁起来,不让里面的线程跑出来。因此,这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计数结束,再开始执行。

对于倒计数器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要对各项设备、仪器进行检查。只有等所有检查都完成后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使点火线程等待所有检查线程全部完工后再执行。CountDownLatch 的构造函数接收一个整数作为参数,即当前这个计数器的计数个数。

注意这里的等待也就是end.await();,也就是计数达到之后,就相当于执行力 end.signal

public CountDownLatch(int count)

下面这个简单的示例,演示了CountDownLatch的使用方法。

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchDemo implements Runnable {
    //计数数量为10表示需要10个线程完成任务后等待在 CountDownLatch 上的线程才能继续执行
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();


    @Override
    public void run() {
        try {
            //模拟检查任务
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete!");
            //通知CountDownLatch,一个线程 已经完成任务,倒计数器减1
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            exec.submit(demo);
        }
        //主线程等待所有检查任务全部完成,等待10个任务全部完成后,主线程才能继续执行,也就是会解除 await
        end.await();
        System.out.println("Fire!");
        exec.shutdown();
    }
}

上述案例的执行逻辑可以用图3.1简单表示。

在这里插入图片描述
主线程在CountDownLatch上等待,当所有检查任务全部完成后,主线程方能继续执行。

二、循环栅栏:CyclicBarrier

CyclicBarrier 是另外一种多线程并发控制工具。和 CountDownLatch 非常类似,它也可以实现线程间的计数等待,但它的功能比 CountDownLatch 更加复杂且强大。

CyclicBarrier可以理解为循环栅栏。栅栏就是一种障碍物,比如,通常在私人宅邸的周围就可以围上一圈栅栏,阻止闲杂人等入内。这里当然就是用来阻止线程继续执行,要求线程在栅栏外等待。前面Cyclic意为循环,也就是说这个计数器可以反复使用。比如,我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,接着凑齐下一批10个线程,这就是循环栅栏内在的含义。

CyclicBarrier 的使用场景也很丰富。比如,司令下达命令,要求10个士兵一起去完成一项任务。这时就会要求10个士兵先集合报到,接着,一起雄赳赳,气昂昂地去执行任务。当10个士兵把自己手上的任务都执行完了,那么司令才能对外宣布,任务完成!

CyclicBarrier 比CountDownLatch 略微强大一些,它可以接收一个参数作为barrierAction。所谓barrierAction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示计数总数,也就是参与的线程总数。

public CyclicBarrier{int parties,Runnable barrierAction}

下面的示例使用CyclicBarrier演示了上述司令命令士兵完成任务的场景。

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic, String soldier) {
            this.cyclic = cyclic;
            this.soldier = soldier;
        }

        @Override
        public void run() {
            try {
                //等待所有士兵到齐,也就是当第一个计数达到后解除等待
                cyclic.await();
                doWork();
                //等待所有士兵完成工作,也就是第二个计数达到后解除等待
                cyclic.await();
            } catch (InterruptedException e) {//处理异常
                e.printStackTrace();
            } catch (BrokenBarrierException e) {//处理异常
                e.printStackTrace();
            }
        }

        public void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + "任务完成");
        }
    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:【士兵" + N + "个,任务完成!】");
            } else {
                System.out.println("司令:【士兵" + N + "个,集合完毕!】");
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        final int N = 5;
        Thread[] allSoldiers = new Thread[N];
        boolean flag = false;
        //创建CyclicBarrier实例,同时将计数器设置为5,要求在计数器达到指标时执行BarrierRun下的run()方法
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
        System.out.println("集合队伍");
        for (int i = 0; i < N; i++) {
            System.out.println("士兵" + i + "报道");
            allSoldiers[i] = new Thread(new Soldier(cyclic, "士兵" + i));
            allSoldiers[i].start();
        }
    }

}

每一个士兵线程都会执行第13行定义的run()方法。

在第16行,每一个士兵线程都会等待,直到所有的士兵都集合完毕。
集合完毕意味着CyclicBarrier的一次计数完成,当再一次调用CyclicBarrier.await()方法时,会进行下一次计数。

第30行模拟了士兵的任务。当一个士兵任务执行完,他就会要求CyclicBarrier开始下一次计数,这次计数主要目的是监控是否所有的士兵都已经完成了任务。一旦任务全部完成,第40行定义的BarrierRun就会被调用,打印相关信息。

在这里插入图片描述
CyclicBarrier.await()方法可能会抛出两个异常。一个是InterruptedException,也就是在等待过程中,线程被中断,应该说这是一个非常通用的异常。大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。

另外一个异常则是CyclicBarrier特有的BrokenBarrierException。一旦遇到这个异常,则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有线程到齐了。如果继续等待,可能就是徒劳无功的,因此,还是“打道回府”吧!上述代码第23~27行处理了这两种异常。

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值