CountDownLatch 一个神奇的计数器,您了解吗

news2024/11/23 12:38:06

一、CountDownLatch基础概念及案例

   1.CountDownLatch是java.util.concurrent 包下提供一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完成再执行。其本质就是一个计数器,传入一个初始的值,调用await 方法会阻塞当前线程,直到调用countDown方法计数器递减为0时,唤起等待线程,等待线程开始执行。

  2. 概念貌似比较抽象,我们一起看看CountDownLatch 使用场景。

  • 模拟100个线程并发执行,直接上代码。田径比赛一声枪响,运动健儿同时开跑。

    public class CountDownLatchTest {
        /*
         * 计数器初始化值为1
         */
        private static CountDownLatch countDownLatch = new CountDownLatch(1);
        /*** 
         * @description:  开启100个线程,并发执行 
         * @param: [args] 
         * @return: void 
         * @author: ppx
         * @date: 2023-07-21 19:31
         */ 
        public static void main(String[] args) {
            for(int i=0;  i< 100; i++){
                new Thread(()->{
                    try {
                        // 线程等待,加入共享模式队列
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName());
                },"thread"+i).start();
            }
            // 计数器减1变成0,触发阻塞线程,并发执行
            countDownLatch.countDown();
        }
        
    }
    

  • 一个线程等待其他线程执行完成后,再继续执行,类似join 方法,多用于不同线程之间的协作。

    public static void main(String[] args) {
            //计数器初始值设置为3
            CountDownLatch latch = new CountDownLatch(3);
            for (int i = 0; i < 3; i++) {
                new Thread(() -> {
                    try {
                        System.out.println("子线程:" + Thread.currentThread().getName() + "开始执行");
                        Thread.sleep(100);
                        System.out.println("子线程:" + Thread.currentThread().getName() + "执行完成");
                        // 计数器每次调用减1,当计数器为0时,唤起等待的线程
                        latch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
    
            }
    
            try {
                System.out.println("主线程:" + Thread.currentThread().getName() + "等待子线程执行完成...");
                //阻塞主线程,直到计数器为0
                latch.await();
                System.out.println("主线程:" + Thread.currentThread().getName() + "被唤起,再次执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
  • 执行结果:

主线程:main等待子线程执行完成...子线程:Thread-0开始执行子线程:Thread-2开始执行子线程:Thread-1开始执行子线程:Thread-2执行完成子线程:Thread-1执行完成子线程:Thread-0执行完成主线程:main被唤起,再次执行

二、CountDownLatch 原理及核心方法 

  1. CountDownLatch 核心方法

方法名描述
CountDownLatch(int count)构造函数,传入计数器的初始值
await()
当前线程插入同步队列并处于等待状态, 直到计数器值为0时或线程被中断时,当前线程被唤醒并再次执行
countDown()每调用一次计数器减1,当计数器为0时,此方法唤起同步队列中等待的线程

2. 源码分析

  • CountDownLatch(int count) 方法

   CountDownLatch基于AQS 实现,本质就是设置Sync 同步状态的值,对CountDownLatch 增加了一个计数器值设置的概念。


  public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    
  // CountDownLatch  内部基于内部类Sync  实现,Sync 继承 AbstractQueuedSynchronizer 
  private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }
        
     /**
     * Sets the value of synchronization state.
     * 设置同步状态值(技数器的值)
     * 
     */
    protected final void setState(int newState) {
        state = newState;
    }
}
  • await()方法
    
    public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
     }

    调用sync的acquireSharedInterruptibly的方法

  public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
         //尝试获取同步锁,stat 小于0 执行获取同步可中断模式
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

 获取同步锁状态,state 与 new CountDownLatch(int count)传入的计数器的值密切相关,state 不等于0才执行doAcquireSharedInterruptibly(arg)方法,把当前线程插入到同步队列中。

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

 获取锁的共享模式,插入当前线程到同步队列中


private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException 
         // 将当前线程加入同步队列尾部,内部基于cas 机制
        final Node node = addWaiter(Node.SHARED);
        try {
            for (;;) {
                // 获取当前node节点的前驱节点
                final Node p = node.predecessor();
                // 如果是头部节点
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    // 判断计数器的值,同步状态值 大于0,设置为头节点
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        return;
                    }
                }
                //非头部节点 逻辑处理 检查并更新未能获取锁的节点的状态
                // 通过LockSupport.park 暂停当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
}
 

图片

countDown()方法

递减latch直到latch的值为0,latch值为0时,释放等待的线程。countDown()底层调用AQS子类sync的releaseShared方法。
 public void countDown() {
        sync.releaseShared(1);
  }

 releaseShared调用tryReleaseShared(arg)方法,tryReleaseShared方法获取同步锁状态并判断是否为0,为0时返回true,唤起同步队列中的线程

 public final boolean releaseShared(int arg) {
        // 尝试获取释放共享锁额状态,为true时,   
        if (tryReleaseShared(arg)) {
            //唤起同步队列中的线程        
            doReleaseShared();
            return true;
        }
        return false;
    }
protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            // 自旋的方式,通过CAS机制比较,最终判断状态(计数器)是否为0            
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
 }
    
    

 
最终调用doReleaseShared方法,自旋唤起队列中等待的线程

private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            // 自旋设置头结点        
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 头节点额等待状态是否SIGNAL             
                if (ws == Node.SIGNAL) {
                    //cas 机制检查 Node.SIGNAL 与0 比较                
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 唤起  后继线程 本质通过(park ,unpark )实现                
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

图片

三、CountDownLatch 

    CountDownLatch 的计数器只无法重置,只能使用一次。如果需要重复使用倒数计数功能,只能重建一个新的 CountDownLatch。CyclicBarrier 可以弥补这个缺陷。

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

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

相关文章

vue+Element项目中v-for循环+表单验证

如果在Form 表单里有通过v-for动态生成&#xff0c;如何设置验证呢&#xff1f; <el-form ref"ruleFormRef" :model"ruleForm" status-icon :rules"rules" label-width"120px"class"demo-ruleForm" hide-required-aster…

大数据课程综合实验案例---课设问题汇总

最近翻看两年前的大数据课设&#xff0c;感觉这个大数据课设实验当时答辩 在大数据课设实验过程中&#xff0c;我遇到了很多问题&#xff0c;在这里做出汇总&#xff1a; 1、MySQL启动报错 首先&#xff0c;我的MySQL有时候启动不了&#xff0c;当我输入这个命令的时候&#…

ModuleNotFoundError: No module named sklearn

前言 出现ModuleNotFoundError: No module named sklearn’的debug过程记录 步骤 安装机器学习库&#xff0c;需要注意报错的sklearn是scikit-learn缩写。 pip install scikit-learn 完成&#xff0c;不再报错

剖析Linuxptp中ptp4l实现--OC

源码克隆地址&#xff1a; git://git.code.sf.net/p/linuxptp/code 项目官网文档&#xff1a; https://linuxptp.nwtime.org/documentation/ 关于linuxptp的相关配置可以参考以下博文&#xff1a; linuxptp/ptp4l PTP时钟同步配置选项 代码剖析 ptp4l的main函数在ptp4l.…

“学习嵌入式开发:明确目标,提升技能“

嵌入式领域涵盖广泛&#xff0c;不可能一次性掌握所有知识。因此&#xff0c;明确学习目标和方向非常重要。选择感兴趣且与职业发展相关的领域进行深入学习是明智之举。 嵌入式技术在不断发展&#xff0c;过去与现在存在差异。选择学习当前行业的主流技术和趋势是明智选择。掌…

基于STM32设计的人体健康监护系统(华为云IOT)

一、设计需求 1.1 设计需求总结 根据需求,要求设计一款基于 STM32 的人体健康监护系统。采用系统模块化思路进行,将多个数模传感器收集到的数据和操作指令一并送至 STM32 中心处理器进行处理分析。 该系统可以实时监测被测者的心率、体温以及周围环境的温度,也同时可以通…

生物信息学_玉泉路_课堂笔记_06 第六章 基因组学:遗传变异分析以及FGWAS

&#x1f345; 课程&#xff1a;生物信息学_玉泉路_课堂笔记 中科院_2022秋季课 第一学期 &#x1f345; 个人笔记使用 &#x1f345; 2023/7/21 课程回顾 第六章 基因组学&#xff1a;遗传变异分析以及FGWAS 第一节 SNP 与 Indel 的鉴定与分析 基本概念 参考基因组 和基因组…

tinkerCAD案例:9.Twist Earrings 扭耳环

tinkerCAD案例&#xff1a;9.Twist Earrings 扭耳环 In this lesson you learn how to create earrings by using cylinder shapes. Let’s get started! 在本课中&#xff0c;您将学习如何使用圆柱形制作耳环。让我们开始吧&#xff01; 说明 Drag a Cylinder shape to the w…

为Android构建现代应用—— 练习状态管理

介绍 本章是一个应用上一章&#xff1a;设计原则中学到的概念的项目。 项目的目标包括以下实现&#xff1a; • 创建一个应用程序&#xff0c;该应用程序使用View作为真实来源。 • 修改应用程序&#xff0c;使其使用ViewModel作为真实来源。 • 将状态和事件进行分组&#x…

N型光伏电池技术“两头开花”,谁是诗和远方?

光伏产业已经进入大规模、市场化发展的新阶段。 近日&#xff0c;国家能源局公布了上半年全国电力工业统计数据&#xff0c;根据总装机容量&#xff0c;光伏装机已正式成为我国第二大电源装机&#xff0c;仅次于煤电。 作为新兴产业&#xff0c;光伏市场持续扩容总是伴随着技…

SpringMVC----(1)基础

SringMVC 1 SpringMVC简介2 SpringMVC入门案例2.1 入门案例2.2 入门案例工作流程2.3 bean加载控制2.4 PostMan工具 3 请求与响应3.1 请求映射路径3.2 Get和Post请求发送普通参数3.3 请求头的五种类型参数传递3.4 请求体的JSON数据传输参数3.5 日期型参数3.6 响应 4 REST风格4.1…

SpringCloud整合Nacos配置中心

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 &#xff0c;关注我&#xff0c;不迷路 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术…

NoSQL之Redis配置使用

目录 一、关系数据库与非关系型数据库 1.1.关系型数据库的概述 1.2关系型数据库的优缺点 1.2.1优点 1.2.2缺点 1.3.非关系型数据库的概述 二.关系数据库与非关系型数据库的区别 2.1数据存储方式不同 2.2扩展方式不同 2.3对事务性的支持不同 2.4非关系型数据库产生背景 2…

亚马逊攀岩绳EN892:2012+A1:2016安全带标准、攀岩安全带EN 12277:2015登山装备要求和ASTM F1772–17体育运动安全标准规范

如果您在亚马逊商城发布商品&#xff0c;则必须遵守适用于这些商品和商品信息的所有联邦、州和地方法律以及亚马逊政策&#xff08;包括本政策&#xff09;。 本政策适用的攀岩安全带 本政策适用于主要在攀岩或登山期间使用且使用者双脚不接触地面时使用的安全带。安全带是一种…

Xshell使用sftp传输文件

单击工具栏新建回话图标&#xff0c;在弹出的新建回话窗口中协议选择SFTP&#xff0c;输入主机名或ip地址&#xff0c;端口号22&#xff0c;单击连接&#xff0c;输入用户名和密码完成创建连接。 本地/远程目录设置&#xff1a;新建会话时在下图中SFTP中设置文件上传下载的本地…

基于vue+element 分页的封装

目录标题 项目场景&#xff1a;认识分页1.current-page2.page-sizes3.page-size4.layout5.total6.size-change7.current-change 封装分页&#xff1a;创建paging&#xff1a;进行封装 页面中使用&#xff1a;引入效果 项目场景&#xff1a; 分页也是我们在实际应用当中非常常见…

Nginx与Tomcat服务器的区别以及个人网站部署方案

- Nginx和Tomcat作用一样吗&#xff1f; 答&#xff1a;不完全相同。Nginx 和 Tomcat 都可以作为 Web 服务器&#xff0c;但它们的作用略有不同。 Nginx 是一个高性能的 Web 服务器和反向代理服务器。它的主要作用是提供静态文件服务、反向代理、负载均衡、缓存、SSL 加密等功…

《PyTorch深度学习实践》

文章目录 1.线性模型2.梯度下降算法3.反向传播3.1原理3.2Tensor in PyTorch 4.用PyTorch实现线性模型 1.线性模型 2.梯度下降算法 # 梯度下降x_data [1.0,2.0,3.0] y_data [2.0,4.0,6.0]w 3.0def forward(x):return x*w# 损失函数 def cost(xs,ys):cost 0for x,y in zip(x…

VR全景在酒店的发展状况如何?酒店该如何做营销?

现阶段&#xff0c;VR全景技术已经被酒店、民宿、旅游景区、房产楼盘、校园等行业所应用&#xff0c;每天都有不少人通过VR全景展示来了解酒店的设施环境&#xff0c;而酒店也可以借此机会&#xff0c;详细展示自身优势&#xff0c;更大范围吸引顾客。 VR酒店拥有真实、立体的全…

某商业落地充电桩后台服务器通迅协议V4.9.

充电机智能终端与智能中心管理系统 通迅协议 目录 一、网络拓扑 4 1.1 功能界定 4 1.1.1 充电机智能终端 4 1.1.2 智能中心管理系统 4 1.2 接口定义 4 1.3 通信方式 4 1.4 通信规约 5 1.5 报文格式 7 1.6 关键命令 8 二、应用层 9 2.1 数据格式&#xff1a; 9 2.2…