专栏首页王磊的博客CountDownLatch:别浪,等人齐再团!

CountDownLatch:别浪,等人齐再团!

一入王者深似海,从此对象是路人。

哈喽观众老爷们你们好,在下战神吕布字奉先,今天给大家来一部吕布的教学视频!

咳咳,不对。大家好,我是磊哥,今天给大家来一篇 CountDownLatch 的文章。

在开始之前,先问大家一个非常专业的技术性问题:打团战最怕_____?

一道非常简单的送分题,如果答不对,那磊哥就要批评你了,哈哈。

可能有人会说:打团战最怕猪队友,但比猪队友更可怕的是打团战人不齐啊兄弟,想想在打团时如果是 5V2 是怎么一幅画面,心痛到不敢想??‍♀️。

等人齐再团

磊哥在儿子没有出生之前,也是资深的农药玩家,至于段位吗?别问!问就是青铜。虽然磊哥的段位不是很高,但基本的大局观还是有的,毕竟也是打过几年 Dota 和 LOL 的青铜玩家是吧?哈哈。

农药和其他 Moba 类游戏是一样的,想要取胜,必须要把握好每次团战,而每次团战的关键在于等人齐了再开团,是吧?而这个思想正好和咱们要讲得 CountDownLatch 的思想是一致的,咱们来看看是怎么回事吧。

吾有上将“CountDownLatch”

想象一下这样一个场景,当我们需要等待某些线程执行完之后,再执行主线程的代码,要怎么实现?

可能有人会说,简单,用 join() 方法等待线程执行完成之后再执行主线程就行了,实现代码是这样的:

// 创建线程1
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});
t1.start();

// 创建线程2
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});
t2.start();

// 等待线程 1和线程 2 执行完
t1.join();
t2.join();

当然,如果使用的是 Thread 来执行任务,那这种写法也是可行的。然而真实的(编码)环境中我们是不会使用 Thread 来执行多任务的,而是会使用线程池来执行多任务,这样可以避免线程重复启动和销毁所带来的性能开销,实现代码如下:

// 创建固定线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 任务一
executorService.submit(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});
// 任务二
executorService.submit(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});

那么这时候问题来了,线程池是没有 join() 方法的,那要怎么实现等待呢?

这个时候就要派出我方大将“CountDownLatch”啦。

吾有上将潘凤,可斩华雄... 出场数秒,潘凤...“卒”。

等等导演,我觉得剧情应该是这样的...

CountDownLatch使用

为了实现等待所有线程池执行完之后再执行主线程的逻辑,我决定使用 AQS(AbstractQueuedSynchronizer,抽象同步框架)下的著名类 CountDownLatch 来实现此功能,具体的实现代码如下:

public static void main(String[] args) throws InterruptedException {
    // 创建 CountDownLatch
    CountDownLatch countDownLatch = new CountDownLatch(2);

    // 创建固定线程数的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    // 任务一
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            // do something
            try {
                // 让此任务执行 1.2s
                Thread.sleep(1200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是任务一");
            countDownLatch.countDown();
        }
    });
    // 任务二
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            // do something
            try {
                // 让此任务执行 1.2s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是任务二");
            countDownLatch.countDown();
        }
    });
    
    // 等待任务执行完成
    countDownLatch.await();
    System.out.println("程序执行完成~");
}

以上程序执行结果如下:

从上述结果可以看出,主线程的执行是等待任务一和任务二都执行完成之后才执行的。

CountDownLatch实现原理

CountDownLatch 中 count down 是倒数的意思,latch 则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有点“321,芝麻开门”的感觉,CountDownLatch 的作用也正是如此。

CountDownLatch 在创建的时候需要传入一个整数,在这个整数“倒数”到 0 之前,主线程需要一直挂起等待,直到其他的线程都执行之后,主线才能继续执行。

CountDownLatch执行流程

CountDownLatch 的实现是在其内部创建并维护了一个 volatile 类型的整数计数器,当调用 countDown() 方法时,会尝试将整数计数器 -1,当调用 wait() 方法时,当前线程就会判断整数计数器是否为 0,如果为 0,则继续往下执行,如果不为 0,则使当前线程进入等待状态,直到某个线程将计数器设置为 0,才会唤醒在 await() 方法中等待的线程继续执行。

CountDownLatch常用方法

// 线程被挂起直到 count 值为 0 才继续执行
public void await() throws InterruptedException { };   

// 和 await() 类似,只不过等待一定的时间后 count 值还没变为 0 的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  

// 将 count 值减 1
public void countDown() { }; 

总结

使用 CountDownLatch 可以实现等待所有任务执行完成之后再执行主任务的功能,它就好像比赛中要等待所有运动员都完成比赛之后再公布排名一样,当然我们在玩农药的时候也是一样,要等所有人集合完毕之后再开团,这是制胜的关键。而 CountDownLatch 是通过计数器来实现等待功能的,当创建 CountDownLatch 时会设置一个大于 0 的计数器,每次调用 countDown() 方法时计数器的值会 -1,直到计数器值变为 0 时,等待的任务就可以继续执行了。

参考 & 鸣谢

www.jianshu.com/p/128476015902

关注公号「Java中文社群」查看更多精彩且有趣的文章!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java并发工具篇

    前面我们介绍了 JUC 中的并发容器,它相当于一个同步容器的升级版,很大程度上提高了并发的性能

    汤圆学Java
  • CountDownLatch、CyclicBarrier让线程听我号令

    在多线程协作完成任务的时候,有时候需要等待其他线程完成任务后,主线程才能继续执行,我们可以使用 Thread 类的 join() 方法,让主线程等待被 join...

    码哥字节
  • Java多线程工具类之循环栅栏计数器

    本文主要内容:CyclicBarrier(下文中凯哥就用cycBar来代替)定义介绍;举例说明;代码演示;从源码来看原理及总结;CyclicBarrier与Co...

    凯哥Java
  • Java多线程编程-(8)-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    CountDownLatch是一个非常实用的多线程控制工具类,称之为“倒计时器”,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

    Java后端技术
  • 并发工具类Phaser、Exchanger使用

    Phaser 是一个更加复杂和强大的同步辅助类,对 CountDownLatch 与 CyclicBarrier 的全面升级,是一个 java 并发 api 的...

    java乐园
  • 【原创】Java并发编程系列22 | 倒计时器CountDownLatch

    并发编程中常遇到这种情况,一个线程需要等待另外多个线程执行后再执行。遇到这种情况你一般怎么做呢?今天就介绍一种JDk提供的解决方案来优雅的解决这一问题,那就是倒...

    java进阶架构师
  • Java 并发(8)CyclicBarrier 源码分析

    现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等...

    一个优秀的废人
  • 非常有用的并发控制-循环栅栏CyclicBarrier

    昨天我讲了倒计时器CountDownLatch的应用,它是阻塞线程直到计时器归0的一种等待方式。今天讲的这个循环栅栏CyclicBarrier与倒计时器非常类似...

    Java技术栈
  • 探索JAVA并发 - 同步工具类

    闭锁的作用相当于一扇门,在这扇门没打开前,任何线程执行到这里都会被无情扣押,直到有人打开了这扇门,那些阻塞在门外的线程才会继续进行门后的流程。

    acupt

扫码关注云+社区

领取腾讯云代金券