CountDownLatch的原理

news2024/11/26 2:21:32

使用CountDownLatch可以实现等待多个线程执行完毕的功能,实现线程之间的协调,让它们按照我们期望的顺序执行,从而避免了可能出现的并发问题。

CountDownLatch是如何实现主线程等待子线程全部结束的呢?

在这里插入图片描述

代码用例
这里我们使用一段测试代码来理解它的原理,demo 如下:

public static void main(String[] args) throws InterruptedException {
    int nThreads = 5; // 需要等待的线程数
    CountDownLatch latch = new CountDownLatch(nThreads);
    Random random = new Random();
    for (int i = 0; i < nThreads; i++) {
        new Thread(() -> {
            // 执行任务
            System.out.println(Thread.currentThread().getName() + " is running...");
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " finished...");
            // 任务完成,计数器减1
            latch.countDown();
        }).start();
    }

    // 等待所有线程执行完毕
    latch.await();
    System.out.println("All threads have finished.");
}

这个例子中,我们创建了一个CountDownLatch实例,并将需要等待的线程数设置为5。然后,我们启动5个线程来执行任务,每个线程执行的时间并不相同,并在每个线程执行完任务后调用 countDown() 方法将计数器减1。

最后,在主线程中我们调用 await() 方法来使当前线程等待,直到所有线程完成任务并计数器减为 0 为止。

原理
关键代码其实就三行。

new CountDownLatch(5) 创建 CountDownLatch 实例,设置为state 为 5,相当于是个信号量。
latch.countDown()任务执行完成,分别减少 state。
latch.await()主线程检查其他线程是否全部执行完成,否则等待。

信号量:那是多线程同步用的,一个线程完成了某一个动作就通过信号告诉别的线程,别的线程再进行某些动作。

互斥量:这是多线程互斥用的,比如说,一个线程占用了某一个资源,那么别的线程就无法访问,知道这个线程离开,其他的线程才开始可以利用这个资源。

创建对象
在创建 CountDownLatch 实例的时候,传入一个参数 5。它实现了内部类 Sync, 并且 Sync 继承了 AbstractQueuedSynchronizer(AQS)类。这里设置的 State 其实就是 AQS 中的 state 成员变量。

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

执行countDown
设置完线信号量之后,在每个线程执行结束后会执行 latch.countDown()将 state 减一。

private final Sync sync;
public void countDown() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

在 releaseShared 方法中,他会尝试 state 减1,这里是一个死循环,也就是自旋锁。通过CAS的方式线程安全的修改 state的值,如果不成功,就通过自旋锁不断去尝试。

private static final class Sync extends AbstractQueuedSynchronizer {
   
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    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;
            //CAS
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

在状态更新后会进入 doReleaseShared ,他会解锁这个队列中的所有线程。正常来说,走到这里都是运行状态的。但是如果主线程先走到了 await方法,这个队列中就会唤醒主线程检查state是否等于0。

子线程执行完后就结束了,不会添加到等待队列和同步队列中。

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //这里说明了头节点后面是有节点的,需要被唤醒。
            if (ws == Node.SIGNAL) {
           //CAS更新状态
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            //head.waitStatus=0的情况有两种
            //1、就是head节点没有及时更新,线程被唤醒之后获取到了锁,在更新head之前,又经过一轮循环执行到这。  但是如果节点没有及时更新就会退出。所以执行到这一步只可能是情况2
            //2、head节点及时更新了,但是到了最后一个节点,它的head.waitStatus=0
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //节点没有及时更新,就退出。
        if (h == head)                   // loop if head changed
            break;
    }
}

最后一步:等待
在主线程执行的时候,到达 await方法会检查当前的 state 是否等于0,如果不等于就返回 -1,进入 doAcquireSharedInterruptibly 逻辑。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 检查 state 是否等于0,不等于返回-1
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

在 doAcquireSharedInterruptibly 方法中,会通过自旋锁不断检查 state。第二次自选检查的时候会被阻塞进入队列,然后等待子线程调用 countDown 方法的时候幻醒主线程,继续自选检查。

rivate void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                //自旋不断检查是否state=0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //第二次自旋,park等待
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

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

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

相关文章

【TES720D】青翼科技基于复旦微的FMQL20S400全国产化ARM核心模块

板卡概述 TES720D是一款基于上海复旦微电子FMQL20S400的全国产化核心模块。该核心模块将复旦微的FMQL20S400&#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个50*70mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用…

AN动画基础——元件,组件,散件

【AN动画基础——元件&#xff0c;组件&#xff0c;散件】 元件不同元件的作用影片剪辑按钮图形元件特性 组件组件的作用组件特性 散件散件作用散件特性 本篇内容&#xff1a;认识元件&#xff0c;组件&#xff0c;散件属性 重点内容&#xff1a;元件&#xff0c;组件&#xff…

专业翻译哪家强?插件AI来帮忙!

大多数人一提到翻译软件&#xff0c;想到的应该是某度翻译或者是某歌翻译&#xff0c;日常使用也是用这两个居多&#xff0c;但是这两个甚至市面上常见的翻译软件的效果都不是很好&#xff0c;不能精准翻译到一些专有名词的意思。 那么究竟有没有好用的AI翻译呢&#xff1f;答…

DAY06_瑞吉外卖——用户地址簿功能菜品展示购物车下单

这里写目录标题 1. 用户地址簿功能1.1 需求分析1.2 数据模型1.3 导入功能代码1.4 功能测试 2. 菜品展示2.1 需求分析2.2 前端页面分析2.3 代码开发2.3.1 查询菜品方法修改2.3.2 根据分类ID查询套餐 2.4 功能测试 3. 购物车3.1 需求分析3.2 数据模型3.3 前端页面分析3.4 准备工作…

大坑-MATLAB图片转存时需注意的点

MATLAB中图片的保存和转存有一个巨大的陷阱&#xff0c;我也是在吃了大亏后发现的&#xff0c;正常情况下&#xff0c;MATLAB跑完实验&#xff0c;生成的图片如下 放大后这样 可以方便修改坐标轴标题&#xff0c;最初我就是因为想修改坐标轴标题才给它放大的&#xff0c;因为…

Linux系统之安装ServerBee服务器监控工具

Linux系统之安装ServerBee服务器监控工具 一、ServerBee介绍1.1 ServerBee简介1.2 ServerBee特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、安装ServerBee4.1 下载部署脚本4.2 解压下载文件4.3 部…

全流量安全分析发现内部系统外联异常

内部系统外连监控的重要性在于保护企业的信息安全和预防数据泄露&#xff0c;以下是几个重要的理由&#xff1a; 1、检测异常活动&#xff1a;通过监控内部系统的外连连接&#xff0c;可以及时发现是否有未经授权或异常的链接尝试。这可能表示存在恶意软件、黑客攻击或内部员工…

LED显示屏高刷新率和低刷新率有什么区别

LED显示屏的刷新率是指图像在LED显示屏上更新的速度&#xff0c;也即屏幕上的图像每秒钟出现的次数&#xff0c;它的单位是赫兹&#xff08;Hz&#xff09;。LED显示屏的刷新率越高&#xff0c;图像闪烁感就越小&#xff0c;稳定性也就越高&#xff0c;换言之对视力的保护也越好…

头部品牌集体扑街!2023年9月京东平板电视TOP10品牌排行榜出炉

鲸参谋监测的京东平台9月份平板电视市场最新销售数据已出炉&#xff01; 根据鲸参谋平台的数据显示&#xff0c;9月份&#xff0c;京东平台大家电品类——平板电视的整体销售呈现下滑。具体地&#xff0c;9月平板电视的销量为62万&#xff0c;环比降低约18%&#xff0c;同比降低…

DDD之领域(Domain)和子域(Subdomain)

领域驱动设计系列文章&#xff0c;点击上方合集↑ 1. 领域 领域&#xff08;Domain&#xff09;是一个组织所做的事情以及其中所包含的一切&#xff0c;领域可以表示整个业务系统。 领域&#xff0c;简单来说&#xff0c;是指一个业务或行业领域&#xff0c;例如电商、社交媒…

【C++】C++11—— 包装器

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C11…

虹科 | 解决方案 | 经销商(OEM)方案

针对汽车厂的方案 Pico 科技是PC版示波器的市场先驱&#xff1a;我们屡次获奖的PicoScope示波器被超过20家世界领先的汽车厂选择&#xff0c;用于提高质量和降低成本。PicoScope既是示波器&#xff0c;也是频谱分析仪、NVH分析仪、发动机压缩和蓄电池/起动充电系统检测仪。我们…

ROS opencv 人脸识别

人脸识别需要在输入的图像中确定人脸&#xff08;如果存在&#xff09;的位置、大小和姿态&#xff0c;往往用于生物特征识别、视频监听、人机交互等应用中。2001年&#xff0c;Viola和Jones提出了基于Haar特征的级联分类器对象检测算法&#xff0c;并在2002年由Lienhart和Mayd…

主题教育问题清单及整改措施2023年-主题教育对照六个方面个人剖析材料

无论前方路途多么坎坷&#xff0c;都要保持内心的坚定和勇敢。生活中没有什么不可战胜的困难&#xff0c;只有我们是否愿意去面对和克服。要相信自己的能力&#xff0c;相信自己拥有足够的智慧和力量去应对一切挑战 每一次的努力都不会白费&#xff0c;每一次的奋斗都是在为自己…

Web自动化测试进阶:网页中难点之expected_ conditions的应用与原理

前言 expected_conditions是selenium的一个模块&#xff0c;可以对网页上元素进行判断&#xff0c;一般配合WebDriverWait使用。 详细介绍说明 1、title_is&#xff0c;判断当前页面的标题是否等于预期&#xff0c;返回布尔值 也可以使用driver.title打印当前页面的标题 f…

BI零售数据分析方案,看了就想拥有

一份优秀的零售数据分析方案该是什么样的&#xff1f;应该是能将人、货、场、供、财的数据分析清楚、展现清楚&#xff0c;更是要具备极强的灵活自主性&#xff0c;随时按需分析。奥威BI零售数据分析方案就是这样的一份零售BI标准方案。 奥威BI零售数据分析方案 1、预设以人货…

BIM如何通过3D开发工具HOOPS实现WEB轻量化?

随着建筑行业的数字化转型和信息建模技术的不断发展&#xff0c;建筑信息模型&#xff08;BIM&#xff09;已经成为设计、建造和管理建筑项目的标准。然而&#xff0c;BIM模型通常包含大量的数据&#xff0c;导致在Web上的传输和查看效率低下。为了解决这一挑战&#xff0c;HOO…

【Java基础面试二】、为什么Java代码可以实现一次编写、到处运行?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;一个Java文件里可以有多…

【广州华锐互动】人体血管器官3D动态展示为医学生提供哪些便利?

人体血管器官3D动态展示是一种采用先进的计算机图形技术和立体成像技术&#xff0c;对人体内部结构和功能进行三维可视化的教学方法。这种教学方式以其独特的优势&#xff0c;正在改变传统的解剖学教学模式&#xff0c;为医学教育带来了革新。 首先&#xff0c;3D动态演示能够提…

openssl 之 RSA加密数据设置OAEP SHA256填充方式

背景 如题 环境 openssl 1.1.1l c centos7.9 代码 /** 思路&#xff1a;填充方式自己写&#xff0c;不需要使用库提供的&#xff0c;然后加密时选择不填充的方式加密 关键代码 */ int padding_result RSA_padding_add_PKCS1_OAEP_mgf1(buf, padding_len, (unsigned char*…