# 前言

CountDownLatch 是一种减法计数器,可以让一个或多个线程等待,常应用于 m 个线程需要等待其他 n 个线程执行完某个逻辑,才能继续执行的场景。

# 使用示例

Java 线程同步器

# 源码分析

# sync

同步器中,唯一的变量就是 sync,定义了同步器的同步操作。Sync 继承了 AQS,主要依赖共享锁的相关方法,实现了 tryAcquireShared 和 tryReleaseShared 方法。

# 构造函数

Sync(int count) {
  //state 初始化为 count,然后进行递减操作
  setState(count);
}

# tryAcquireShared

protected int tryAcquireShared(int acquires) {
  //state 为 0 时可以直接获取到锁
  return (getState() == 0) ? 1 : -1;
}

这里为啥 state 为 0 时可以直接获取到锁呢?想一下 CountDownLatch 的使用场景,某些线程需要依赖其他 n 个线程的执行,state 初始为 n,只有这 n 个线程全部释放锁,即将 state 减为 0 时,才表示这 n 个线程执行完毕,从而依赖这 n 个线程的线程不用等待了,可以继续执行了。

# tryReleaseShared

protected boolean tryReleaseShared(int releases) {
  // 自旋
  for (;;) {
    int c = getState();
    // 状态是 0,说明没有要释放的锁了,已经被前 n 个线程释放了
    if (c == 0)
      return false;
    // 状态不是 0,进行减 1 操作
    int nextc = c-1;
    if (compareAndSetState(c, nextc))
      // 返回是否完全释放了锁
      return nextc == 0;
  }
}

# 构造函数

首先是同步器的构造,构造函数需要指定计数器的数量。

public CountDownLatch(int count) {
  if (count < 0) throw new IllegalArgumentException("count < 0");
  // 初始 state 为 count
  this.sync = new Sync(count);
}

# 主要方法

# await

public void await() throws InterruptedException {
    // 直接调用 AQS 方法进行阻塞等待,直到中断或者锁已释放
    sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    // 带超时的
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

# countDown

public void countDown() {
    // 直接调用 AQS 方法进行锁的释放
    sync.releaseShared(1);
}

# 场景说明

前提条件:线程 a 和线程 b 依赖线程 1 和线程 2 的执行。

# ① 初始化一个 count 为 2 的 CountDownLatch

同步器的 state 为 2。

# ② 线程 a 等待

线程 a 调用 await () 进行等待,此时 state 为 2,尝试获取锁失败。然后加入等待队列,等待队列需要初始化,初始化后 head 和 tail 指向同一个 waitStatus 为 0 的空节点。初始化完毕,新节点加入等待队列,tail 变为指向新节点。此时新节点的前置节点是 head,所以再次尝试下能不能获取到锁,不能的话才进行后续的等待。再次尝试获取锁失败,将 head 节点的 waitStatus 设置为 -1,然后新节点进入等待状态。

# ③ 线程 1 执行完毕

线程 1 调用 countDown () 进行释放锁,尝试释放锁时成功将 state 从 2 更新为 1,但未完全释放锁,尝试释放共享锁失败,此时不会进行锁的释放。

# ④ 线程 2 执行完毕,线程 a 继续执行

线程 2 调用 countDown () 将 state 更新为 0,尝试释放为成功。head 的 waitStatus 为 -1,说明有等待的线程需要唤醒。将 head 的 waitStatus 设置为 0,然后唤醒队列中等待的线程。线程 a 从上次等待的位置继续执行,尝试获取锁成功,将当前节点设置为 head,并继续唤醒后面的线程。因为队列中没有其他等待的线程了,所以此次 await 结束,线程 a 继续执行后面的代码。

线程 a 继续执行

# ⑤ 线程 b

线程 b 调用 await () 直接返回,因为此时 state 为 0,直接获取到了锁。

# 总结

CountDownLatch 的逻辑其实很简单,只是对重写了尝试获取锁和尝试释放锁的逻辑,定义 state 什么情况下可以直接获取锁,什么情况下可以直接释放锁,其他的加入等待队列并等待唤醒的操作,直接交给 AQS 就好了。