Java8 CountDownLatch 源码分析

青旅半醒 2022-04-23 13:40 379阅读 0赞

一、CountDownLatch 概述

1.1 什么是 CountDLatch

闭锁(CountDownLatch)是 java.util.concurrent 包下的一种同步工具类。闭锁可以用来确保某些活动直到其他活动都完成后才执行。

闭锁相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当达到结束状态时,这扇门会打开,并允许所有的线程通过。

1.2 CountDownLatch 的应用场景

  • 确保某个计算在其需要的所有资源都被初始化之后才执行
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动
  • 等待直到每个操作的所有参与者都就绪再执行(比如打麻将时需要等待四个玩家就绪)

1.3 CountDownLatch 简单应用

我们知道 4 个人玩纸牌游戏一定会先等所有玩家就绪后才会发牌,下面我们就来用闭锁简单的模拟一下。

  1. public class CountDownLatchTest {
  2. /** * 初始化需要等待的 4 个事件 */
  3. private static CountDownLatch latch = new CountDownLatch(4);
  4. public static void main(String[] args) throws InterruptedException {
  5. // 创建 4 个线程分别代表 4 个玩家
  6. new Thread(() -> { System.out.println("玩家 1 已就绪"); latch.countDown(); }).start();
  7. new Thread(() -> { System.out.println("玩家 2 已就绪"); latch.countDown(); }).start();
  8. new Thread(() -> { System.out.println("玩家 3 已就绪"); latch.countDown(); }).start();
  9. new Thread(() -> { System.out.println("玩家 4 已就绪"); latch.countDown(); }).start();
  10. // 所有玩家就绪前一直阻塞
  11. latch.await();
  12. System.out.println("所有玩家已就绪,请发牌");
  13. }
  14. }

下面是控制台输出:

在这里插入图片描述

二、CountDownLatch 原理分析

CountDownLatch 底层是基于 AQS 实现的,如果不懂 AQS 原理的小伙伴需要先了解下 AQS 再来看这篇文章。

2.1 API 相关方法

构造函数:

  1. public CountDownLatch(int count) {
  2. if (count < 0) throw new IllegalArgumentException("count < 0");
  3. // 初始化 count 值
  4. this.sync = new Sync(count);
  5. }

CountDownLatch 内部有一个 Sync 同步对象,这个对象是一个内部类实现了 AQS,下面我们会具体来看方法实现。

await 方法:

  1. public void await() throws InterruptedException {
  2. // 共享式检查是否中断,如果中断抛出异常
  3. // 调用 tryAcquireShared 方法尝试获取同步状态,当闭锁内的线程执行完毕后尝试获取成功,直接返回
  4. sync.acquireSharedInterruptibly(1);
  5. }

countDown 方法:

  1. public void countDown() {
  2. // 调用 releaseShared 每次使同步状态值减 1
  3. sync.releaseShared(1);
  4. }

通过上面的 API 我们应该能知道其大概的原理了,在 CountDownLatch 初始化的时候会有一个初始的同步状态值,这个同步状态值可以理解为放行前的所要执行的线程数,每次调用 countDown 方法时就把同步状态值减 1,await 方法会自旋检查同步状态值是否为 0,当不为 0 时会阻塞线程,当为 0 时会直接返回,该方法是支持相应 中断的,当线程中断时会抛出异常。因此该方法可以理解为一扇门,只有当指定数量的线程执行完后,才会执行后续的代码。

上面我们已经理解了大概的流程,下面来看下具体的实现代码。

2.2 Sync 同步类

  1. private static final class Sync extends AbstractQueuedSynchronizer {
  2. // 初始化闭锁 count 值
  3. Sync(int count) {
  4. setState(count);
  5. }
  6. int getCount() {
  7. return getState();
  8. }
  9. // 通过共享方式尝试获取锁
  10. protected int tryAcquireShared(int acquires) {
  11. return (getState() == 0) ? 1 : -1;
  12. }
  13. // 通过共享方式尝试释放锁
  14. // 因为该方法是线程共享的,因此需要通过 CAS 操作保证线程安全
  15. protected boolean tryReleaseShared(int releases) {
  16. for (;;) {
  17. int c = getState();
  18. // 同步状态值在上一次置 0 时已经放行,因此返回 false
  19. if (c == 0)
  20. return false;
  21. // 同步状态值 - 1
  22. int nextc = c-1;
  23. // 为 0 时返回 true
  24. if (compareAndSetState(c, nextc))
  25. return nextc == 0;
  26. }
  27. }
  28. }

内部代码很简单,如果你明白了 AQS 的内部原理,这些代码是很容易理解的,如果你对这里的代码感觉到陌生,那么你一定要好好的再去了解下 AQS 了。

AQS 的原理设计的很巧妙,相对来说也比较难理解,后面想梳理这块内容的时候会尝试着总结一下,如果你对这块感兴趣,也可以到我的 GitHub 去看对应的源码。

jdk1.8 源码阅读:https://github.com/zchen96/jdk1.8-source-code-read

参考资料

《Java 并发编程实战》

发表评论

表情:
评论列表 (有 0 条评论,379人围观)

还没有评论,来说两句吧...

相关阅读

    相关 并发工具类CountDownLatch分析

    > 同步工具类可以使任何一种对象,只要该对象可以根据自身的状态来协调控制线程的控制流。阻塞队列可以作为同步工具类,其他类型的同步工具类还包括:信号量(Semaphore)、栅栏