CountDownLatch

一、作用

用于主线程等待多个次线程执行完后再执行。比如API接口要求响应时间在200ms以内,但是如果一个接口内部依赖多个三方/外部服务,那串行调用接口的RT必然很久,所以个人用的最多的是接口RT优化场景,内部服务并行调用。

二、常用方法:

CountDownLatch(int count) //实例化一个倒计数器,count指定计数个数
countDown() // 计数减一
await() //等待,当计数减到0时,所有线程并行执行

三、例子

对于倒计数器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检测。只有等到所有的检查完毕后,引擎才能点火。那么在检测环节当然是多个检测项可以同时进行的。代码实现

public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        MyThread my = new MyThread(latch);
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for(int i = 0; i < 5; i++) {
            executor.submit(my);
        }
        latch.await();
        System.out.println("fire...");
        executor.shutdown();
    }
public class MyThread implements Runnable{
    private CountDownLatch countDownLatch;

    public MyThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(5000L);
            System.out.println("yoxi");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    }
}

打印:

yoxi
yoxi
yoxi
yoxi
yoxi
fire...

CyclicBarrier

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

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

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

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

public CyclicBarrier(int parties, Runnable barrierAction)

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

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

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

        public void run() {
            try {
                // 等待所有士兵到齐
                cyclic.await();
                doWork();
                // 等待所有士兵完成工作
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        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;

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

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

    public static void main(String args[]) throws InterruptedException {
             final int N = 10;
             Thread[] allSoldier=new Thread[N];
             boolean flag = false;
             CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
             //设置屏障点,主要是为了执行这个方法
             System.out.println("集合队伍!");
             for (int i = 0; i < N; ++i) {
                 System.out.println("士兵 "+i+" 报道!");
                 allSoldier[i]=new Thread(new Soldier(cyclic, "士兵 " + i));
                 allSoldier[i].start();
             }
         }
}

输出如下:

集合队伍!
士兵 0 报道!
//篇幅有限,省略其他几个士兵
士兵 9 报道!
司令:[士兵10个,集合完毕!]
士兵 0:任务完成
//篇幅有限,省略其他几个士兵
士兵 4:任务完成
司令:[士兵10个,任务完成!]

 

 CyclicBarrier.await()方法可能会抛出两个异常。一个是InterruptedException,也就是在等待过程中,线程被中断,应该说这是一个非常通用的异常。大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。另外一个异常则是CyclicBarrier特有的BrokenBarrierException。一旦遇到这个异常,则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有线程到齐了。如果继续等待,可能就是徒劳无功的,因此,还是就地散货,打道回府吧!