CountDownLatch 源码分析

本文是建立在了解 AbstractQueuedSynchronizer 的基础上的。

构造时可通过new CountDownLatch(n) 传入 n 作为计数值,调用 countDown() 方法可以递减计数器,在计数器归零之前其他线程调用 await() 方法时会被阻塞,计数变为 0 时会唤醒所有阻塞的线程。若在计数器为 0 时调用 await() 方法会直接返回,不会阻塞。

CountDownLatch 是基于 AbstractQueuedSynchronizer 实现的,简称 AQS,AQS 中包括 acquire 和 release 操作,acquire 操作在失败时会被阻塞,release 操作不会发生阻塞,所以 await() 方法对应 acquire,countDown() 方法对应 release

AQS 这是两种模式,独占(exclusive)和共享(shared)模式。

当以独占模式 acquire 成功时,其他线程的 acquire 都不会成功,会被阻塞。在共享模式下,多个线程的 acquire 可能会成功,也可能不会。

独占模式需要实现 tryAcquire、tryRelease,可选实现 isHeldExclusively

共享模式需要实现 tryAcquireShared、tryReleaseShared

CountDownLatch 计数器归零时,多个阻塞线程会被同时唤醒,而不是只唤醒一个,说明其工作在共享模式下,所以需要实现 tryAcquireShared、tryReleaseShared

  • int tryAcquireShared(int arg)

    • 失败时返回负值;
    • 如果共享模式的 acquire 成功但后续的共享模式 acquire 无法成功,则返回零;
    • 如果共享模式的 acquire 成功且后续的共享模式 acquire 也可能成功,则返回正值,此时后续等待的线程必须检查资源的可用性。
    • (支持三种不同的返回值,使得该方法可用于某些获取操作有时仅独占的情况。)一旦成功,表示该对象已被获取。
  • boolean tryReleaseShared(int arg)

    • 如果此共享模式的释放可能使等待的线程 acquire(共享或独占)成功,则返回 true;否则返回 false

有了上面的推测之后,再进行源码分析就简单多了。

CountDownLatch 的各个方法都是简单的委托给了内部类 Sync

countDown() 委托给了 sync.releaseShared(1)await() 委托给了 sync.acquireSharedInterruptibly(1)

java 复制代码
public class CountDownLatch {

    private final Sync sync;

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

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

Sync 继承自 AbstractQueuedSynchronizer,以 state 存储计数器的值。

java 复制代码
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        // 设置计数器的值
        setState(count);
    }

    int getCount() {
        // 获取计数器的值
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        // 计数器为 0 则返回 1,acquire 成功且后续的 acquire 也会成功
        // 计数器不为 0 则返回 -1, acquire 失败且后续的 acquire 也会失败
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            // 获取当前计时器的值
            int c = getState();
            // 已经为零则 release 失败,且不会唤醒阻塞的线程,因为阻塞的线程已经被为 0 的那一次 release 唤醒了。
            // 计数器为 0 后再调用 await() 不会再阻塞,也就不再需要唤醒
            if (c == 0)
                return false;
            // 计数器减一
            int nextc = c-1;
            // cas 设置新计数器的值,如果失败则循序重试
            if (compareAndSetState(c, nextc))
                // cas 成功后,如果 nextc 不为 0 则返回 false。
                // 如果 nextc 为 0 则返回 true,返回 true 时 AQS 会自动唤醒阻塞的线程。
                return nextc == 0;
        }
    }
}
相关推荐
余额瞒着我当琳1 小时前
C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve
java·开发语言·c++
thefool1122662 小时前
翻转二叉树
java
喜欢打篮球的普通人2 小时前
LLVM Backend Lowering 从入门到实战:把 IR 变成机器码的完整链路
android·java·数据库
FL16238631293 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
jufeng13073 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine
程序员贺加贝3 小时前
轻制造SaaS的生产闭环建模-BOM工单领料报工质检与入库
java·设计模式·架构
敲个大西瓜5 小时前
Spring Could Alibaba 核心面试题
java·后端·spring
骇客野人5 小时前
Java SSO 统一认证方案
java·开发语言
START_GAME5 小时前
MSSQL$SQL2016
java·服务器·前端