干货!CountDownLatch的使用场景

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!前端

image.png

放上面这张图有两个意思java

第一 预祝我国奥运健儿在东京奥运会上搏击长空,再创佳绩,为国争光!面试

第二 跟本节内容有关,上面这幅图更有利于你理解本节内容。数据库

CountDownLatch顾名思义

CountDownLatch,它是 JDK 提供的并发流程控制的工具类,它是在 java.util.concurrent 包下,在 JDK1.5 之后加入的。后端

Count - Down - Latchmarkdown

计数,关闭/向下,门阀 (英文很差,勿喷)并发

那就能够理解为,一道门槛,按照计数量一个一个安排进出。哈哈 这是我我的理解,下面举个正常的例子,让你恍然大悟。dom

举例理解

  • 第一个例子,好比咱们去游乐园坐激流勇进,有的时候游乐园里人不是那么多,这时,管理员会让你稍等一下,等人坐满了再开船,这样的话能够在必定程度上节约游乐园的成本。座位有多少,就须要等多少人,这就是 CountDownLatch 的核心思想,等到一个设定的数值达到以后,才能出发。

image.png

这幅图就很好理解了。ide

能够看到,最开始 CountDownLatch 设置的初始值为 3,而后 T0 线程上来就调用 await 方法,它的做用是让这个线程开始等待,等待后面的 T一、T二、T3,它们每一次调用 countDown 方法,3 这个数值就会减 1,也就是从 3 减到 2,从 2 减到 1,从 1 减到 0,一旦减到 0 以后,这个 T0 就至关于达到了本身触发继续运行的条件,因而它就恢复运行了。函数

  • 第二个例子,就是文章之初放的那幅图。

image.png

这个例子正好与第一个例子相反,第一个是等全部人到了,我才开始。这个例子是等全部运动员跑到终点以后,裁判才会宣布中止。CountDownLatch就是裁判!

主要方法介绍

下面介绍一下 CountDownLatch 的主要方法。

  • 构造函数:public CountDownLatch(int count) {  };

它的构造函数是传入一个参数,该参数 count 是须要倒数的数值。

  • await():调用 await() 方法的线程开始等待,直到倒数结束,也就是 count 值为 0 的时候才会继续执行。

  • await(long timeout, TimeUnit unit):await() 有一个重载的方法,里面会传入超时参数,这个方法的做用和 await() 相似,可是这里能够设置超时时间,若是超时就再也不等待了。

  • countDown():把数值倒数 1,也就是将 count 值减 1,直到减为 0 时,以前等待的线程会被唤起。

CountDownLatch使用场景01

一个线程等待其余多个线程都执行完毕,再继续本身的工做

在实际开发场景中,不少状况下须要咱们初始化一系列的前置操做,好比数据库先创建链接,全部bean都加载完毕,在这些准备条件都完成以前,是不能进行下一步工做的,因此这就是利用 CountDownLatch 的一个很好场景,咱们可让应用程序的主线程在其余线程都准备完毕以后再继续执行。

咱们还拿运动员跑步这个场景用代码模拟下。好比在比赛跑步时有 5 个运动员参赛,终点有一个裁判员,何时比赛结束呢?那就是当全部人都跑到终点以后,这至关于裁判员等待 5 个运动员都跑到终点,宣布比赛结束!

public class RunDemo1 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println(no + "号运动员完成了比赛。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        latch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待5个运动员都跑完.....");
        latch.await();
        System.out.println("全部人都跑完了,比赛结束。");
    }
}
复制代码

在这段代码中,咱们新建了一个初始值为 5 的 CountDownLatch,而后创建了一个固定 5 线程的线程池,用一个 for 循环往这个线程池中提交 5 个任务(5个运动员),每一个任务表明一个运动员,这个运动员会首先随机等待一段时间,表明他在跑步,而后打印出他完成了比赛,在跑完了以后,一样会调用 countDown 方法来把计数减 1。

以后咱们再回到主线程,主线程打印完“等待 5 个运动员都跑完”这句话后,会调用 await() 方法,表明让主线程开始等待,在等待以前的那几个子线程都执行完毕后,它才会认为全部人都跑完了比赛。这段程序的运行结果以下所示:

等待5个运动员都跑完.....
4号运动员完成了比赛。
3号运动员完成了比赛。
1号运动员完成了比赛。
5号运动员完成了比赛。
2号运动员完成了比赛。
全部人都跑完了,比赛结束。
复制代码

能够看出,直到 5 个运动员都完成了比赛以后,主线程才会继续,并且因为子线程等待的时间是随机的,因此各个运动员完成比赛的次序也是随机的。

CountDownLatch使用场景02

多个线程等待某一个线程的信号,同时开始执行

这和第一个用法有点相反,咱们再列举一个实际的场景,好比在运动会上,刚才说的是裁判员等运动员,如今是运动员等裁判员。在运动员起跑以前都会等待裁判员发号施令,一声令下运动员统一块儿跑,咱们用代码把这件事情描述出来,以下所示:

public class RunDemo2 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("运动员有5秒的准备时间");
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(no + "号运动员准备完毕,等待裁判员的发令枪");
                    try {
                        countDownLatch.await();
                        System.out.println(no + "号运动员开始跑步了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
        Thread.sleep(5000);
        System.out.println("5秒准备时间已过,发令枪响,比赛开始!");
        countDownLatch.countDown();
    }
}
复制代码

在这段代码中,首先打印出了运动员有 5 秒的准备时间,而后新建了一个 CountDownLatch,其倒数值只有 1;接着,一样是一个 5 线程的线程池,而且用 for 循环的方式往里提交 5 个任务,而这 5 个任务在一开始时就让它调用 await() 方法开始等待。

接下来咱们再回到主线程。主线程会首先等待 5 秒钟,这意味着裁判员正在作准备工做,好比他会喊“各就各位,预备”这样的话语;而后 5 秒以后,主线程会打印出“5 秒钟准备时间已过,发令枪响,比赛开始”的信号,紧接着会调用 countDown 方法,一旦主线程调用了该方法,那么以前那 5 个已经调用了 await() 方法的线程都会被唤醒,因此这段程序的运行结果以下:

运动员有5秒的准备时间
2号运动员准备完毕,等待裁判员的发令枪
1号运动员准备完毕,等待裁判员的发令枪
3号运动员准备完毕,等待裁判员的发令枪
4号运动员准备完毕,等待裁判员的发令枪
5号运动员准备完毕,等待裁判员的发令枪
5秒准备时间已过,发令枪响,比赛开始!
2号运动员开始跑步了
1号运动员开始跑步了
5号运动员开始跑步了
4号运动员开始跑步了
3号运动员开始跑步了
复制代码

能够看到,运动员首先会有 5 秒钟的准备时间,而后 5 个运动员分别都准备完毕了,等待发令枪响,紧接着 5 秒以后,发令枪响,比赛开始,因而 5 个子线程几乎同时开始跑步了。

总结

刚才讲了两种用法,其实这两种用法并非孤立的,甚至能够把这两种用法结合起来,好比利用两个 CountDownLatch,第一个初始值为多个,第二个初始值为 1,这样就能够应对更复杂的业务场景了;

CountDownLatch 类在建立实例的时候,须要在构造函数中传入倒数次数,而后由须要等待的线程去调用 await 方法开始等待,而每一次其余线程调用了 countDown 方法以后,计数便会减 1,直到减为 0 时,以前等待的线程便会继续运行。

小贴士: 咱们在实际开发中还能够在统计数据中应用,好比咱们的数据展板统计,后台可能有若干个查询统计,那就用此思想,将每一个耗时查询放到线程池里,一块儿跑,让该接口快速响应。

弦外之音

这是我在掘金的第七篇文章了。感谢阅读,并但愿之后持续关注,我会输出更多技术干货,咱们共同进步!

之后可能会分为几大专题,相似于并发专题,源码专题,面试专题等(只会分享干货)。

感兴趣的能够点击我头像查看历史文章,每一篇都是干货哦!

点滴积累,点滴分享,咱们共同成长!

相关文章
相关标签/搜索