可口的JAVA-并发控制之CountDownLatch

前言

本文带大家系统的学习一下CountDownLatch知识,从介绍、方法、场景、原理四个角度方面开展学习,话不多说,马上开始。

一:介绍

CountDownLatch是一种同步辅助工具,允许一个或多个线程等待*其他线程中正在执行的一组操作完成。

CountDownLatch 使用给定的 count 进行初始化。* {await} 方法阻塞,直到当前计数达到零,因为调用了 {countDown} 方法,之后*所有等待线程被释放,并且任何后续调用* {await } 立即返回。这是一种一次性现象,计数无法重置。如果您需要重置计数的版本,请考虑使用 CyclicBarrier。

二:方法

CountDownLatch类对外开放的方法有

  • void await(); 启动线程等待
  • void countDown(); 等待数目减一
  • int getCount(); 获取当前等待线程数目

    ** 分享一段代码帮助大家理解**

    以幼儿园呼叫孩子们做游戏为例子,老师要一个一个通知到孩子,孩子们签到,开始做游戏。 Flowerd.class

    /**
     * @class 孩子
     */
    class Flowerd implements Runnable{
        public Flowerd(CountDownLatch latch_s){
            latch = latch_s;
        }
        private CountDownLatch latch;
        @Override
        public void run() {
            try {
                //1.孩子们反应时间不同,用一个随机数
                Thread.sleep((int)(1000*Math.random()));
                System.out.println(Thread.currentThread().getName()+"::来了,还差【"+(latch.getCount()-1)+"】个人!");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
    
            }
        }
    }
    复制代码

    playGame.method

    /**
     * @function 喊孩子们做游戏
     */
    public static void playGame() {
        System.out.println("开始呼叫孩子们");
        CountDownLatch downLatch = new CountDownLatch(3);
        System.out.println("呼叫小明。。。");
        new Thread(new Flowerd(downLatch),"小明").start();
        System.out.println("呼叫小滑。。。");
        new Thread(new Flowerd(downLatch),"小滑").start();
        System.out.println("呼叫小六。。。");
        new Thread(new Flowerd(downLatch),"小六").start();
        System.out.println("孩子们呼叫完毕");
        try {
            downLatch.await();
            System.out.println("孩子们都来了,开始做游戏吧。。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    复制代码

    执行结果:

    开始呼叫孩子们
    呼叫小明。。。
    呼叫小滑。。。
    呼叫小六。。。
    孩子们呼叫完毕
    小滑::来了,还差【2】个人!
    小六::来了,还差【1】个人!
    小明::来了,还差【0】个人!
    孩子们都来了,开始做游戏吧。。。。
    复制代码

    CountDownLatch初始化了三个孩子,主线程调用await阻塞,子线程调用countDown减一,当三个孩子全部报道后await方法自动释放。 另外,await方法支持设置定时器,超时自动释放。

    boolean await(long timeout, TimeUnit unit)
    复制代码

    替换代码:

    downLatch.await(300l, TimeUnit.MILLISECONDS);
    复制代码

    替换后执行结果:

    开始呼叫孩子们
    呼叫小明。。。
    呼叫小滑。。。
    呼叫小六。。。
    孩子们呼叫完毕
    小六::来了,还差【2】个人!
    小滑::来了,还差【1】个人!
    孩子们都来了,开始做游戏吧。。。。
    小明::来了,还差【0】个人!
    复制代码

    三:场景

    大家了解了CountDownLatchiben方法和特性后,可以进行合理推测:countdownlatch就是一把等待 锁,可以我等你也可以你等我,可以一个资源等多个资源,也可以多个资源等一个资源,延伸一下就是多个资源等多个资源。

    场景一:一个资源等多个资源

    某聚合接口的调用,需要整合底层多个服务调用结果,使用同步调用很显然不行(除非客户没有性能方面要求),异步调用后整合子服务结果返回。

    public static void main(String[] args) {
        callAPI();
    }
    
    /**
     * @function 模拟聚合接口调用
     */
    static void callAPI(){
        CountDownLatch latch = new CountDownLatch(3);
        //调用服务A
        System.out.println("进入api");
        System.out.println("调用A服务");
        FutureTask<Integer> ft1 = new FutureTask<>(new BaseService(latch));
        new Thread(ft1,"底层服务A").start();
        //调用服务B
        System.out.println("调用B服务");
        FutureTask<Integer> ft2 = new FutureTask<>(new BaseService(latch));
        new Thread(ft2,"底层服务B").start();
        //调用服务C
        System.out.println("调用C服务");
        FutureTask<Integer> ft3 = new FutureTask<>(new BaseService(latch));
        new Thread(ft3,"底层服务C").start();
    
        try {
            System.out.println("等待子服务调用结果。。。");
            latch.await();
            System.out.println("API结果为:【"+(ft1.get()+ft2.get()+ft3.get())+"】");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (ExecutionException e){
            e.printStackTrace();
        }
    }
    复制代码
    /**
     * 可调用底层服务
     */
    class BaseService implements Callable<Integer>{
        private CountDownLatch latch;
        public BaseService(CountDownLatch latch1){
            latch = latch1;
        }
        @Override
        public Integer call() throws Exception {
            Thread.sleep((int)Math.random()*1000);
            Integer re = (int)(Math.random()*10000);
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"服务执行完毕,返回【"+re+"】");
            return re;
        }
    }
    复制代码

    执行结果:

    进入api
    调用A服务
    调用B服务
    调用C服务
    等待子服务调用结果。。。
    底层服务A服务执行完毕,返回【5643】
    底层服务C服务执行完毕,返回【639】
    底层服务B服务执行完毕,返回【3524】
    API结果为:【9806】
    复制代码

    场景二:多个资源等一个资源

    运动场上,选手准备完毕后,等待裁判发令枪响,然后同时起跑。 示例代码:

    /**
     * 运动员
     */
    class RunMan implements Runnable{
        public RunMan(CountDownLatch latch_s){
            latch = latch_s;
        }
        private CountDownLatch latch;
        @Override
        public void run() {
            System.out.println("运动员【"+Thread.currentThread().getName()+"】准备完毕,等待裁判发令枪响。。。");
            try {
                latch.await();
                Thread.sleep((int)Math.random()*1000);
                System.out.println("运动员【"+Thread.currentThread().getName()+"】到达终点");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    复制代码
    public static void main(String[] args) {
        raceArea();
    }
    /**
     * 赛场
     */
    static void raceArea(){
        System.out.println("开始准备");
        CountDownLatch downLatch = new CountDownLatch(1);
        new Thread(new RunMan(downLatch),"小明").start();
        new Thread(new RunMan(downLatch),"小滑").start();
        new Thread(new RunMan(downLatch),"小六").start();
        try {
            Thread.sleep(2000);
            System.out.println("裁判员开枪。。。。");
            downLatch.countDown();
            Thread.sleep(2000);
            System.out.println("比赛结束");
    
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
    }
    复制代码

    执行结果:

    开始准备
    运动员【小滑】准备完毕,等待裁判发令枪响。。。
    运动员【小六】准备完毕,等待裁判发令枪响。。。
    运动员【小明】准备完毕,等待裁判发令枪响。。。
    裁判员开枪。。。。
    运动员【小滑】到达终点
    运动员【小明】到达终点
    运动员【小六】到达终点
    比赛结束
    复制代码

    场景三:多个资源等多个资源

    例如:汽车和电瓶车过马路,首先要保证是绿灯,其次要保证没有行人正在闯红灯横穿马路,我们可以设置两个线程(行人+绿灯)做等待条件,再设置两个线程(汽车和电瓶车)做要执行的条件,与上述两个功能都有类似之处,代码大家调整修改一下就OK了,搞不定的评论一下我再写。

    四:部分原理

    要点一:CountDownLatch内部使用了 AbstractQueuedSynchronizer(fifo)抽象同步队列的来保证同步。 源码01:

    image.png

    要点二:采用cas乐观锁控制CountDownLatch的递减,重写了tryReleaseShared()方法。源码02:

    image.png

    要点三:CountDownLatch是不可重复指定的,只能初始化一次,这点和CyclicBarrier有区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值