JUC之AQS-CountDownLatch小结
从本文开始我们对Java并发包中的AQS相关类进行学习及总结,首先了解的是java.util.concurrent.CountDownLatch类。
AQS,即AbstractQueuedSynchronized,中文翻译为抽象队列式同步器。它定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…。
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,而是等待指定的时间后就恢复了执行。