找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1887

积分

0

好友

248

主题
发表于 7 天前 | 查看: 18| 回复: 0

在 Java 并发编程中,CountDownLatch 是一个非常实用的同步辅助类,常用于协调多个线程之间的执行顺序,例如让主线程等待若干工作线程全部完成后再继续执行。而其底层实现的核心,正是 AQS(AbstractQueuedSynchronizer)

本文将深入剖析 CountDownLatch 如何基于 AQS 构建自己的同步逻辑,并揭示 AQS 在并发工具类中的典型使用模式。

一、AQS 的通用使用三步法

在理解 CountDownLatch 之前,先掌握 AQS 的标准使用范式至关重要。通常,若要基于 AQS 实现自定义同步器,需遵循以下三步:

  1. 定义内部 Sync 类
    创建一个内部类(通常命名为 Sync),继承 AbstractQueuedSynchronizer
  2. 重写获取/释放方法
    根据同步器是“独占”还是“共享”模式,重写对应的方法:
    • 独占模式:tryAcquire()/tryRelease()
    • 共享模式:tryAcquireShared()/tryReleaseShared()
  3. 封装对外 API
    在主类中提供用户友好的方法(如 await()countDown()),并在内部调用 AQS 的模板方法(如 acquireSharedInterruptibly()releaseShared())。

⚠️ 注意:AQS 中的 tryXXX 方法默认抛出 UnsupportedOperationException,因此子类必须重写相关方法,否则运行时会报错。

二、CountDownLatch 的 AQS 实现解析

2.1 内部 Sync 类定义

CountDownLatch 的核心逻辑封装在其私有静态内部类 Sync 中:

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    Sync(int count) {
        setState(count); // 初始化 AQS 的 state
    }
    int getCount() {
        return getState(); // 获取剩余计数
    }
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c - 1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}
  • state 的含义:表示“还需倒数的次数”。初始值由构造函数传入。
  • 共享模式:因为多个线程可以同时“通过门闩”(即 await 成功),所以使用共享锁语义。

2.2 构造函数:初始化倒计数值

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
  • 将用户指定的 count 传递给 Sync 构造函数。
  • Sync 调用 setState(count),将 AQS 的 state 初始化为倒计数值。

2.3 await():等待门闩打开

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
  • 调用 AQS 的 acquireSharedInterruptibly 方法。
  • 该方法首先检查中断状态,然后调用 tryAcquireShared
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
  • state == 0,说明倒数已完成,返回 1(表示获取成功),线程继续执行;
  • 否则返回 -1,AQS 会将当前线程加入等待队列并阻塞,直到被唤醒。

2.4 countDown():减少计数并尝试开门

public void countDown() {
    sync.releaseShared(1);
}
  • 调用 AQS 的 releaseShared,内部会调用 tryReleaseShared
protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0) return false; // 已经归零,无需操作
        int nextc = c - 1;
        if (compareAndSetState(c, nextc)) {
            return nextc == 0; // 只有归零时才返回 true
        }
    }
}
  • 使用 CAS 原子地将 state 减 1;
  • 仅当 nextc == 0 时返回 true,此时 AQS 会调用 doReleaseShared(),唤醒所有等待线程。

✅ 关键点:只有最后一次 countDown() 才会触发线程唤醒,避免重复唤醒开销。

三、工作流程总结

  1. 初始化new CountDownLatch(N) → AQS 的 state = N
  2. 等待:调用 await() 的线程检查 state
    • state > 0,进入 AQS 队列阻塞;
    • state == 0,直接放行。
  3. 倒数:每次 countDown()state 原子减 1。
  4. 开门:当 state 减至 0 时,AQS 唤醒所有阻塞线程,后续 await() 调用不再阻塞。

四、为何选择 AQS?

Doug Lea 在设计 AQS 时,刻意采用“继承 + 重写”而非“接口实现”,原因在于:

  • 接口要求实现所有方法,但不同同步器只需部分功能;
  • 继承允许按需重写,更灵活;
  • 默认方法抛出异常,强制子类明确行为,避免误用。

这种设计使得 AQS 成为一个高度可复用、安全且高效的同步框架。

五、扩展思考:其他工具类的 AQS 应用

  • Semaphorestate 表示许可数量,acquire() 对应 acquireSharedrelease() 对应 releaseShared
  • ReentrantLock:使用独占模式,state 表示重入次数。
  • CyclicBarrier:虽然也用于线程协调,但不基于 AQS,而是通过 ReentrantLock + Condition 实现。

因此,CountDownLatch 是 AQS 共享模式的经典范例。

结语

通过 CountDownLatch 的源码分析,我们不仅理解了其工作机制,更掌握了 AQS 在实际并发工具中的应用范式。AQS 将复杂的线程排队、阻塞、唤醒等底层细节封装起来,让开发者只需关注业务状态(如倒计数)的转换逻辑,极大提升了开发效率与系统稳定性。深入理解这些并发工具背后的Java并发框架,是通往高级工程师的必经之路。

掌握 AQS,就等于握住了 Java 并发世界的“万能钥匙”。如果你想了解更多关于并发编程的知识,欢迎来云栈社区与更多开发者一同交流探讨。




上一篇:未来5-10年,具备业务洞察与技术整合能力的程序员更具竞争力
下一篇:美团半年记:在技术氛围浓厚的大厂团队工作是种怎样的体验?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 08:51 , Processed in 0.335263 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表