【同步工具类:CountDownLatch】

news2025/1/11 15:10:36

同步工具类:CountDownLatch

  • 介绍
  • 源码分析
    • 继承图
    • 核心方法分析
      • await()
      • countDown()
  • 业务场景
    • 代码实现
    • 测试结果
  • 总结

介绍

Jdk原文翻译
CountDownLatch 一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
CountDownLatch用给定的计数初始化。由于countDown方法的调用,await方法会阻塞,直到当前计数达到零,然后释放所有等待线程,然后立即返回await的任何后续调用。这是一种一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用CyclicBarrier。
CountDownLatch是一种通用的同步工具,可用于多种目的。用计数1初始化的CountDownLatch用作一个简单的开/关锁存器或门:所有调用countDown的线程都在门处等待,直到它被调用countDown的线程打开。初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个操作,或某个操作完成N次。
CountDownLatch的一个有用的特性是,它不要求调用countDown的线程在继续之前等待计数达到零,它只是防止任何线程通过等待,直到所有线程都可以通过。
通俗理解
CountDownLatch 是一个多线程同步工具类,在多线程环境中它允许多个线程处于等待状态,直到前面的线程执行结束,从类名上看CountDown既是数量递减的意思,我们可以把它理解为计数器

源码分析

继承图

在这里插入图片描述

核心方法分析

await()

如下所示,await() 调用的是AQS的模板方法。然后 CountDownLatch.Sync 重新实现了 tryAccuqireShared方法。
从tryAccuqireShared 方法的实现来看,只要 state!=0,调用 await()方法的线程便会被放入AQS的阻塞队列,进入阻塞状态。

 public void await() throws InterruptedException {
 		//调用 AQS 的模板方法
        sync.acquireSharedInterruptibly(1);
    }
 
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
            //如果其他线程调用interrupt ,也可以唤醒 此线程
        if (Thread.interrupted())
            throw new InterruptedException();
            //CountDownLatch.Sync 重新实现了 tryAcquireShared 方法
        if (tryAcquireShared(arg) < 0)
        	//只要state!=0,就会放入阻塞队列进行阻塞,进入阻塞状态
            doAcquireSharedInterruptibly(arg);
    }

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

countDown()

countDown() 调用的AQS的模板方法 releaseShared(),里面的tryReleaseShared()被 CountDownLatch.Sync重新实现。
从下面的代码可以看出,只有state 的状态成功被改成了0。tryReleaseShared()方法才会返回true.然后执行doReleaseShared()。一次性唤醒队列中所有阻塞的线程。

 public void countDown() {
 		//调用AQS的模板方法
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) {
        //CountDownLatch.Sync 重新实现了 tryReleaseShared 方法
        if (tryReleaseShared(arg)) {
        	//唤醒队列中所有阻塞的线程
            doReleaseShared();
            return true;
        }
        return false;
    }
 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;
                //只有 c==1, 减一后变成0.然后将 c 的值改成0.此时才返回true.
                //然后唤醒阻塞队列中的所有阻塞线程
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

业务场景

有个人脸识别应用,比如我们常见的上班打卡应用。应用首次启动要求多线程将存储在DB中的人脸数据加载到本地应用中,主线程需要等待所有子线程完成任务后,才能继续执行余下的业务逻辑,比如加载 dubbo组件啥的。

代码实现

此处使用了两个CountDownLatch ,一个是用于准备DB读取的准备工作,另一个是等待DB读取工作完成后开始加载其他组件。

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(10);
        for (int i = 0; i < 10; ++i) // create and start threads
             new Thread(new DoDemo(startSignal, doneSignal)).start();
        doSomethingElse();
        //开始加载数据,
        startSignal.countDown();
        //主线程阻塞,等待数据加载完成
         doneSignal.await();
        doSomethingElse();
        System.out.println("数据加载完成,开始启动其他组件,包括dubbo组件");
    }

    public static void doSomethingElse(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class DoDemo extends Thread {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;

    public DoDemo(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }

    @Override
    public void run() {
        try {
            //开始阻塞了,等待主线程开启
            System.out.println(Thread.currentThread().getName()+" 开始阻塞等待了...");
            startSignal.await();
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        }
    }

    public void doWork() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " 开始干活了,DB 中的数据加载到本地缓存中");
        Thread.sleep(1000);
    }
}

测试结果

在这里插入图片描述

总结

多线程并发的情况下需要做好同步处理,结合CountDownLatch 充分的运用到业务场景当中还是挺有必要的,凡是需要在多个任务执行完成后再去做另一件事的情况都可以考虑使用CountDownLatch。合理使用但请不要滥用,特别上面也提到过计数值需要确定,否则可能导致多任务无法做到同步甚至造成主线程无限等待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css3的重点内容

css3的重点内容 浮动 父级边框塌陷问题 浮动的清除 clear:left; //清除左侧浮动 clear:right; //清除右侧浮动 clear:both; //清除两侧浮动解决方案 增加父级元素的高度增加一个空的div&#xff0c;之后清除浮动通过overflow来进行相关元素的修剪给父类添加相应的伪类元素…

使用 docker 部署 MySQL 会导致数据丢失吗

2023年2月28日&#xff0c;今天下午电话面试 java 岗位&#xff0c;经过一些提问后&#xff0c;面试官问了一个问题&#xff0c;“那么你最近在关注什么方面的技术点呢&#xff1f;”&#xff0c;可能是我之前的回答不太理想&#xff0c;且说辞都是“不好意思&#xff0c;可能最…

JS逆向-百度翻译sign

前言 本文是该专栏的第36篇,后面会持续分享python爬虫干货知识,记得关注。 有粉丝留言,近期需要做个翻译功能,考虑到百度翻译语言语种比较全面,但是它的参数被逆向加密了,对于这种情况需要怎么处理呢?所以本文以它为例。 废话不多说,跟着笔者直接往下看正文详细内容。…

狂飙狂飙,一年都沉浸在狂飙中,教你们用python来实现在图片中添加日历

前言 我是小废物&#xff0c;才开始看狂飙&#xff0c;这热点还能蹭上吗 该说不说&#xff0c;是真的很喜欢里面的大嫂啊&#xff0c;现在壁纸都一直是她&#xff0c;大美女谁不喜欢啊 昨天还是前天刷到了她出席活动&#xff0c;太帅了吧 不多说&#xff0c;先看大美女 &am…

【面试1v1实景模拟】Redis面试官会怎么提问?本文助你面试入坑,赶快收藏吧~

笑小枫专属目录老面&#x1f474;&#xff1a;小伙子&#xff0c;咱来聊聊Redis&#xff0c;你知道什么是Redis吗?老面&#x1f474;&#xff1a;除了key-value类型的数据&#xff0c;你还知道Redis的哪几种数据结构?老面&#x1f474;&#xff1a;你说说在什么场景下使用了R…

taobao.opensecurity.uid.get( open security uid 获取接口 )

&#xffe5;免费不需用户授权 根据明文 taobao user id 换取 app的 open_uid 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, …

2023年湖北助理工程师(初级职称)怎么评?需要什么资料?启程别

2023年湖北助理工程师&#xff08;初级职称&#xff09;怎么评&#xff1f;需要什么资料&#xff1f;启程别 助理工程师主要是指初级工程技术人员的职务名称&#xff0c;他是通过相关考试和相关部门评审通过之后所获得的相应名称&#xff0c;想要了解职称更多相关资料可以咨询启…

使用字典快速输入编号(匹配编码表)

实例需求&#xff1a;在A列输入样品名称之后&#xff0c;在B列字段填写编号&#xff0c;其规则如下&#xff1a; 如果A列已经存在相同样品名称&#xff0c;则将编号递增&#xff0c;例如&#xff1a;A15输入沥青&#xff0c;最后一行相同样品名称在第12行&#xff0c;B15填写递…

第十届蓝桥杯省赛——4质数(质数判断,数学函数:开方函数)

题目&#xff1a;试题 D: 质数本题总分&#xff1a;10 分【问题描述】我们知道第一个质数是 2、第二个质数是 3、第三个质数是 5……请你计算第 2019 个质数是多少&#xff1f;【答案提交】这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数&…

环形缓冲区(c语言)

1、概念介绍 在我们需要处理大量数据的时候&#xff0c;不能存储所有的数据&#xff0c;只能先处理先来的&#xff0c;然后将这个数据释放&#xff0c;再去处理下一个数据。 如果在一个线性的缓冲区中&#xff0c;那些已经被处理的数据的内存就会被浪费掉。因为后面的数据只能…

使用码匠连接一切|二

目录 Elasticsearch Oracle ClickHouse DynamoDB CouchDB 关于码匠 作为一款面向开发者的低代码平台&#xff0c;码匠提供了丰富的数据连接能力&#xff0c;能帮助用户快速、轻松地连接和集成多种数据源&#xff0c;包括关系型数据库、非关系型数据库、API 等。平台提供了…

软测入门(一)测试理念及基础知识

软测入门理念 软件的分类 按层次划分&#xff1a;系统软件、应用软件按组织划分&#xff1a;商业软件、开源软件按结构划分&#xff1a;单机软件、 软件缺陷 由来 Grace Hopper发明Cobol计算机语言&#xff0c;也是找出电脑程序中第一个bug的女程序员 BugDefect 定义 软…

Kafka面试问题总结

kafka架构2.基础概念Producer&#xff08;生产者&#xff09; : 产生消息的一方。Consumer&#xff08;消费者&#xff09; : 消费消息的一方。Broker&#xff08;代理&#xff09; : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。同时&#x…

SpringBoot整合

14-SpringBoot整合Junit 搭建SpringBoot工程 引入starter-test起步依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency>&…

化繁为简高效部署 华为云发布部署服务CodeArts Deploy

​随着互联网、数字化的发展&#xff0c;公司机构与各类企业往往需要进行大量频繁的软件部署&#xff0c;部署设备类型多样&#xff0c;如&#xff1a;本地机器、云上裸金属服务器、云上虚拟机与容器等。面对多种部署模式、分布式复杂运行环境&#xff0c;如何用最短时间、高质…

2021年NOC复赛真题解析

本题来自2021年NOC创新编程复赛操作题。阿短是一位小学五年级学生,本学期即将结束,阿短打算创作一个能够进行四则运算练习的出题程序。程序能够随机出10道题,题目皆是一位数的加减乘除四则运算,每答对一题得1分。请你帮助阿短,编写出这个程序吧。 1.参与运算的两个数值都…

分享5款Windows同类软件中的翘楚

今天要给大家推荐的是5款软件&#xff0c;每个都是同类软件中的个中翘楚&#xff0c;请大家给我高调地使用起来&#xff0c;不用替我藏着掖着。1.沙盒工具——Sandboxie Sandboxie是一个电脑必备的沙盘工具&#xff0c;对于从网上下载的软件安装包、文件、视频、图片等等一切不…

一道阿里类的初始化顺序笔试题

问题很简单&#xff0c;就是下面的代码打印出什么&#xff1f; public class InitializeDemo {private static int k 1;private static InitializeDemo t1 new InitializeDemo("t1" );private static InitializeDemo t2 new InitializeDemo("t2");priv…

硬件工程师常见问题与答疑

在工作中&#xff0c;尤其是做了很多年的&#xff0c;有些问题可能不知道&#xff0c;又不好意思问&#xff0c;怕别人说你连这个都不知道&#xff1f;很尴尬&#xff0c;而且百度又搜不到&#xff0c;本博主收集了很多答疑&#xff0c;希望里面有对你有用的&#xff0c;或者是…

linux修改DNS 系统版本Kylin V10桌面版

配置DNS在银河麒麟桌面操作系统V10 SP1 中修改DNS信息&#xff0c;直接修改/etc/resolv.conf文件中的DNS信息&#xff0c;不能生效。应该参考如下步骤&#xff1a;一、首先修改 /etc/systemd/resolved.conf文件&#xff0c;在其中添加DNS信息在终端中执行以下命令&#xff1a;s…