快来看看!AQS 和 CountDownLatch 有怎么样的关系?

简介: CountDownLatch 一个同步辅助工具,同样是基于 AQS 实现,本篇文件主要是介绍 CountDownLatch 的使用,以及源码。

网络异常,图片无法展示
|


前言


CountDownLatch 一个同步辅助工具,同样是基于 AQS 实现,本篇文件主要是介绍 CountDownLatch 的使用,以及源码。


介绍

一个同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止


一个 CountDownLatch 初始化为给定计数。 在 await 方法阻塞,调用 countDown 方法会减少计数直到达到零,此后所有等待的线程被释放,任何后续调用 await 都会立即返回。 这是一次性的现象 - 计数不能复位。 如果你需要一个版本重置计数,请考虑使用CyclicBarrier 。


CountDownLatch 是一种通用的同步工具,可用于多种用途。

  1. 用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用 countDown 的线程打开。
  2. 初始化计数为 N ,用一个线程等待,直到 N 个线程完成某项操作,或某些动作已经完成 N 次。

CountDownLatch 一个有用的属性是,它不要求调用 countDown 线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程可以通过。


基本使用

在我之前 CAS 那篇文章《从JUC源码看CAS,我做了个笔记 ......》中介绍 CAS 举例时使用了 CountDownLatch,其代码如下:

public class CasTest {
    private static final CountDownLatch LATCH = new CountDownLatch(10);
    private static int NUM_I = 0;
    private static volatile int NUM_J = 0;
    private static final AtomicInteger NUM_K = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        NUM_I++;
                        NUM_J++;
                        NUM_K.incrementAndGet();
                    }
                    LATCH.countDown();
                }
            });
        }
        LATCH.await();
        System.out.println("NUM_I = " + NUM_I);
        System.out.println("NUM_J = " + NUM_J);
        System.out.println("NUM_K = " + NUM_K.get());
        threadPool.shutdown();
    }
}

简单介绍下这段代码的主要逻辑及功能:

  1. CountDownLatch 初始化计数为 10 。
  2. 开 10 个线程去处理业务逻辑,业务逻辑结束会调用 LATCH.countDown() 对计数进行 -1 操作。
  3. 在 LATCH.await() 处会阻塞等待,直到 LATCH 的值为 0 ,即 10 个线程业务都处理结束。
  4. 然后主线程继续执行。


问题疑问

  1. CountDownLatch 和 AQS 有什么关系?
  2. CountDownLatch 的实现原理是什么?


源码分析


基本结构

网络异常,图片无法展示
|

通过类图可以看出,CountDownLatch 内部存在一个静态类 Sync,而 Sync 继承了 AbstractQueuedSynchronizer。具体内部是如何实现的,则下面通过源码和画图一步一步的进行介绍。


初始化

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


通过初始化构造器可以看出,在 new 创建对象时必须传递一个 int 类型的非负数。实现逻辑可以看出,是创建了一个 Sync 对象。

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    Sync(int count) {
        setState(count);
    }
    int getCount() {
        return getState();
    }
}   


之前在介绍 AQS 源码中已经介绍了 state 的含义,state 在不同子类中代表不同的含义。

  1. 在 ReentrantLock 中 state 代表加锁状态,0 没有线程获得锁,大于等于 1 已经有线程获得锁,大于 1 说明该获得锁的线程多次重入。
  2. 在 ReentrantReadWriteLock 中 state 代表锁的状态。state 为 0 ,没有线程持有锁,state 的高 16 为代表读锁状态,低 16 为代表写锁状态。通过位运算可以获取读写锁的实际值。
  3. 而在这里 (CountDownLatch)则代表门闩或者说计数的值。


countDown

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

递减锁存器的计数:

  • 如果当前计数大于零,则递减。
  • 如果计数到达零,则释放所有等待的线程。
  • 如果那么当前计数等于零没有任何反应。

此处调用的是 AQS 的 releaseShard() 方法,释放共享资源。

// AQS 代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}


在 AQS 释放共享资源方法中 tryReleaseShared(arg) 部分是在 CountDownLatch 的内部类 Sync 中实现的,代码部分如下:

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}


递减更新 state ,如果 state 为 0 则返回 false,否则返回 true 。

此时再对照上面 AQS 代码,发现:如果 tryReleaseShared 返回 true ,则会唤醒后续节点开始执行操作。所以也就是说,如果 state 不为 0,则不会唤醒后续节点,直到 state 为 0 。


await

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

导致当前线程等待,直到锁存器倒计数至零,除非线程被中断。

  • 如果当前计数为零,则此方法立即返回。
  • 如果当前计数大于零,则当前线程用于线程调度目的,禁用并一直处于休眠状态的两件事情之一发生:
  • 因调用countDown方法使计数达到0;
  • 其他某些线程中断当前线程。
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}


AQS 定义了 tryAcquireShared 返回值分为 3 种:

  1. 小于 0: 表示失败;
  2. 等于 0: 表示共享模式获取资源成功,但后续的节点不能以共享模式获取成功;
  3. 大于 0: 表示共享模式获取资源成功,后续节点在共享模式获取也可能会成功,在这种情况下,后续等待线程必须检查可用性。

其中 tryAcquireShared 同样由 CountDownLatch 的内部类 Sync 中实现,内部逻辑主要是判断 state 的值,进行返回。

在内部实现中返回的值只有 1 和 -1 ,说明在 state == 0 时,返回 1 ,即唤醒后续节点。不等于 0 时,会阻塞。

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}


总结


Q: CountDownLatch 和 AQS 有什么关系?

A: CountDownLatch 是基于 AQS 的共享模式实现的。


Q: CountDownLatch 的实现原理是什么?

A: 可以参考上面的源码解析,进行总结介绍。 CountDownLatch 是基于 AQS 共享模式实现的,在初始化时必须传入计数,该计数实际上是 AQS 的 state 值。在 countDown 时对 state 进行递减,在 当 state 为 0 时 会唤醒 AQS 队列中的所有等待的节点 (因为是共享模式)。而 await 方法是判断 state 的值,如果不是 0 ,则所有线程在队列中阻塞,等待唤醒。


Q: state 在代表的含义是什么?

A:

  1. 在 ReentrantLock 中 state 代表加锁状态,0 没有线程获得锁,大于等于 1 已经有线程获得锁,大于 1 说明该获得锁的线程多次重入。
  2. 在 ReentrantReadWriteLock 中 state 代表锁的状态。state 为 0 ,没有线程持有锁,state 的高 16 为代表读锁状态,低 16 为代表写锁状态。通过位运算可以获取读写锁的实际值。
  3. 而在这里 (CountDownLatch)则代表门闩或者说计数的值。
目录
相关文章
|
数据采集 自然语言处理 算法
如何使用Python的Gensim库进行自然语言处理和主题建模?
使用Gensim库进行Python自然语言处理和主题建模,包括:1) 安装Gensim;2) 导入`corpora`, `models`, `nltk`等相关模块;3) 对文本数据进行预处理,如分词和去除停用词;4) 创建字典和语料库;5) 使用LDA算法训练模型;6) 查看每个主题的主要关键词。代码示例展示了从数据预处理到主题提取的完整流程。
399 3
|
3月前
|
存储 缓存 算法
软考通关密钥:计算机系统核心原理全解剖——软件设计师必懂的底层逻辑
专为软考中级软件设计师打造,深入解析CPU架构、存储体系、进制转换、原码补码、浮点数、寻址方式、校验码、RISC/CISC、流水线、Cache、中断、I/O控制、总线及加密技术等核心知识点,结合真题剖析高频考点,构建计算机系统底层知识体系,提升应试与实践能力。
287 1
软考通关密钥:计算机系统核心原理全解剖——软件设计师必懂的底层逻辑
|
3月前
|
存储 安全 API
Typora介绍、安装、图床设置和使用
Typora是一款简洁高效的Markdown编辑器,支持实时预览、数学公式编辑、表格制作与多格式导出,适用于写作、学术与技术文档。本文详细介绍其安装、激活、主题设置、图床配置(PicGo+SM.MS/COS/OSS)及常用功能使用,助你高效搭建写作环境。
662 3
Typora介绍、安装、图床设置和使用
|
7月前
|
关系型数据库 MySQL 数据库
【赵渝强老师】数据库不适合Docker容器化部署的原因
本文介绍了在Docker中部署MySQL数据库并实现数据持久化的方法,同时分析了数据库不适合容器化的原因。通过具体步骤演示如何拉取镜像、创建持久化目录及启动容器,确保数据安全存储。然而,由于数据安全性、硬件资源争用、网络带宽限制及额外隔离层等问题,数据库服务并不完全适合Docker容器化部署。文中还提到数据库一旦部署通常无需频繁升级,与Docker易于重构和重新部署的特点不符。
397 18
【赵渝强老师】数据库不适合Docker容器化部署的原因
|
前端开发 JavaScript
electron ui框架
Electron是一个使用JavaScript, HTML和CSS等前端技术构建跨平台桌面应用程序的框架。
692 0
|
安全 应用服务中间件 Shell
网站在后台启用了https协议之后重新登录就不进去后台的解决方法
网站在后台启用了https协议之后重新登录就不进去后台的解决方法
|
开发者 Python
Python中的正则表达式:re模块详解与实例
Python中的正则表达式:re模块详解与实例
501 0
|
缓存 开发框架 前端开发
SpringCloud微服务实战——搭建企业级开发框架(四十一):扩展JustAuth+SpringSecurity+Vue实现多租户系统微信扫码、钉钉扫码等第三方登录
  如果我们自己的系统需要调用第三方登录,那么我们就需要实现单点登录客户端,然后跟需要对接的平台调试登录SDK。JustAuth是第三方授权登录的工具类库,对接了国外内数十家第三方登录的SDK,我们在需要实现第三方登录时,只需要集成JustAuth工具包,然后配置即可实现第三方登录,省去了需要对接不同SDK的麻烦。   JustAuth官方提供了多种入门指南,集成使用非常方便。但是如果要贴合我们自有开发框架的业务需求,还是需要进行整合优化。下面根据我们的系统需求,从两方面进行整合:一是支持多租户功能,二是和自有系统的用户进行匹配。
9493 56
SpringCloud微服务实战——搭建企业级开发框架(四十一):扩展JustAuth+SpringSecurity+Vue实现多租户系统微信扫码、钉钉扫码等第三方登录
|
监控 安全 网络安全
网络信息安全之信息系统安全保障
信息系统是用于采集、处理、存储、传输、分发和部署信息的整个基础设施、组织结构、人员和组件的总和。随着当前社会信息化程度的不断提高,各类信息系统越来越成为其所从属的组织机构生存和发展的关键因素,信息系统的安全风险也成为组织风险的一部分。同时,信息系统受来自于组织内部与外部环境的约束,信息系统的安全保障除了要充分分析信息系统本身的技术、业务、管理等特性,还要考虑这些约束条件所产生的要求。为了保障组织机构完成使命,系统安全管理人员必须针对信息系统面临的各种各样的风险制定相应的策略。
928 0

热门文章

最新文章