一、CountDownLatch概述

CountDownLatch 构造一个用给定计数初始化的并发计数器,能够协调多个线程之间的同步,当前线程在计数器倒计数至零之前一直等待,除非线程被中断。一般用于流程控制之类的场景,大流程分成多个子流程,子流程全部结束后大流程开始操作。

二、使用示例

使用 CountDownLatch 控制主线程等待所有子线程执行完成之后继续执行。

public static void main(String[] args) throws InterruptedException {
    int n = 4;
    CountDownLatch latch = new CountDownLatch(n);
    for (int i = 0; i < n; i++) {
        new Thread(() -> {
            System.out.println("子线程执行");
            latch.countDown();
        }).start();
    }
    latch.await();
    System.out.println("子线程执行完成");
}

输出结果:

子线程执行
子线程执行
子线程执行
子线程执行
子线程执行完成

三、执行原理

CountDownLatch 内部是通过共享锁的原理实现的,其中包含一个继承自 AbstractQueuedSynchronizerSync 类,当 CountDownLatch 初始化时就会给锁状态 state 指定一个初始的值(可以理解为锁的重入次数)。

调用 await 方法就会直调 AbstractQueuedSynchronizer 获取共享锁的方法 acquireSharedInterruptibly,因为初始已经是有一个初始值了,所以会被阻塞。

调用 countDown 方法就会直调 AbstractQueuedSynchronizer 释放共享锁的方法 releaseShared 执行一次释放锁操作,当锁状态到达 0 则锁表示已经被完全释放了,这时候 await 阻塞的所有线程都被唤醒。

使用共享锁才能让所有被 await 阻塞的线程同时唤醒,如果是独占锁则只会唤醒一个线程。

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    // 设置初始化的锁状态
    Sync(int count) {
        setState(count);
    }
    // 取得获取锁次数
    int getCount() {
        return getState();
    }
    // 尝试获取共享锁,如果锁状态为0则返回1表示获取锁成功,否则获取锁失败
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    // 进行一次锁释放
    protected boolean tryReleaseShared(int releases) {
        // CAS有并发冲突的可能,需要循环获取
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            // 锁状态-1
            int nextc = c-1;
            // CAS进行锁释放
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

关于 AbstractQueuedSynchronizer 中共享锁和独占锁流程的实现,见锁系列文章。