文章目录
  1. 1. CountDownLatch概述
  2. 2. CountDownLatch使用
    1. 2.1. CountDownLatch典型用法1
    2. 2.2. CountDownLatch典型用法2
  3. 3. CountDownLatch的不足之处
  4. 4. 一个例子

从本文开始我们对Java并发包中的AQS相关类进行学习及总结,首先了解的是java.util.concurrent.CountDownLatch类。

AQS,即AbstractQueuedSynchronized,中文翻译为抽象队列式同步器。它定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…。

本文先不展开讲解AQS,它值得用一篇完整的文章来总结。

CountDownLatch概述

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说进行线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。

它使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。

当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。

CountDownLatch使用

那么我们如何使用CountDownLatch来进行线程之间的协调操作呢?

CountDownLatch典型用法1

某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n

CountDownLatch countDownLatch = new CountDownLatch(n) 

每当一个任务线程执行完毕,就将计数器减1

countdownlatch.countDown()

当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。

一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

CountDownLatch典型用法2

实现多个线程开始执行任务的最大并行性。

注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。

类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

CountDownLatch的不足之处

当然,CountDownLatch不是完美的,它是一次性的,计数器的值只能在初始化时构造一次,之后没有任何机制能够对它重设值。也就是说,当CountDownLatch使用完毕之后不能再次使用。

一个例子

我们用一个例子讲解CountDownLatch如何使用。

private final static int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {

        ExecutorService exec = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    LOGGER.warning("execption:" + e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        countDownLatch.await(10, TimeUnit.MILLISECONDS);    // 指定时间内完成
        LOGGER.info("finish !!!");
        exec.shutdown();
    }

我们指定了计数器为200,让主线程await,等待200个线程执行完毕。每个子线程在执行完毕都对计数器进行减一操作。

当200个子线程都执行完毕,主线程才执行。

......
七月 12, 2018 5:17:51 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 7
七月 12, 2018 5:17:51 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 6
七月 12, 2018 5:17:51 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 4
七月 12, 2018 5:17:51 下午 com.snowalker.test.aqs.CountDownLatchDemo main
信息: finish !!!

如果我们不想一直等待,可以指定线程等待时间,通过

countDownLatch.await(10, TimeUnit.MILLISECONDS);    // 指定时间内完成

这里我指定为10毫秒,可以看到我们的主线程没有等待所有线程执行完毕才被唤醒,而是等待10毫秒后恢复执行,输出finish!!!

七月 12, 2018 5:18:36 下午 com.snowalker.test.aqs.CountDownLatchDemo main
信息: finish !!!
七月 12, 2018 5:18:36 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 5
七月 12, 2018 5:18:36 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 4
七月 12, 2018 5:18:36 下午 com.snowalker.test.aqs.CountDownLatchDemo test
信息: threadNum = 7

可以看到,在指定等待时间的情况下,主线程并没有一直等待计数器为0,而是等待指定的时间后就恢复了执行。

文章目录
  1. 1. CountDownLatch概述
  2. 2. CountDownLatch使用
    1. 2.1. CountDownLatch典型用法1
    2. 2.2. CountDownLatch典型用法2
  3. 3. CountDownLatch的不足之处
  4. 4. 一个例子
Fork me on GitHub