CountDownLatch倒计时器源码解读与使用

这篇具有很好参考价值的文章主要介绍了CountDownLatch倒计时器源码解读与使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

CountDownLatch倒计时器源码解读与使用,Java源码解读,java,开发语言

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

CountDownLatch倒计时器源码解读与使用,Java源码解读,java,开发语言

目录

1. 前言

2. CountDownLatch有什么用

3. CountDownLatch底层原理

3.1. countDown()方法

3.2. await()方法

4. CountDownLatch的基本使用

5. 总结


1. 前言

在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器)、CAS操作等知识,而 CountDownLatch 又是JUC包下一个比较常见的同步工具类,我们今天就继续来学一下这个同步工具类!

2. CountDownLatch有什么用

我们知道AQS是专属于构造锁和同步器的一个抽象工具类,基于它Java构造出了大量的常用同步工具,如ReentrantLock、Semaphore、ReentrantReadWriteLock、SynchronousQueue等等,我们今天的主角CountDownLatch同样如此。

CountDownLatch(倒时器)允许N个线程阻塞在同一个地方,直至所有线程的任务都执行完毕。CountDownLatch 有一个计数器,可以通过countDown()方法对计数器的数目进行减一操作,也可以通过await()方法来阻塞当前线程,直到计数器的值为 0。

这个就很类似我们的Moba类游戏的游戏加载过程,所有玩家几乎是一起进入游戏,加载快的玩家要等待加载慢的玩家,只有当全部玩家加载完成才能进入游戏,而CountDownLatch就类似于这个过程中的发令枪。

3. CountDownLatch底层原理

想要迅速了解一个Java类的内部构造,或者使用原理,最快速直接的办法就是看它的源码,这是很多初学者比较抵触的,会觉得很多封装起来的源码都晦涩难懂,诚然很多类内部实现是复杂,我也是慢慢从刚开始阅读Mybatis源码,到后来阅读JDK多线程相关的源码,尝试培养自己看源码的习惯,硬着头皮看段时间还是有不少收获的。

好的,我们直接进入CountDownLatch内部去看看它的底层原理吧

//几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS
private static final class Sync extends AbstractQueuedSynchronizer {
     private static final long serialVersionUID = 4982264981922014374L;
     Sync(int count) {
         setState(count);
     }
     int getCount() {
         return getState();
     }
 }
private final Sync sync;
//构造方法中初始化count值
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

是不是很熟悉?对又是Sync,与 Semaphore信号量一样,几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS

3.1. countDown()方法

//核心方法,内部封装了共享模式下的线程释放
 public void countDown() {
 	//内部类Sync,继承了AQS
     sync.releaseShared(1);
 }
 //AQS内部的实现
 public final boolean releaseShared(int arg) {
  if (tryReleaseShared(arg)) {
  		//唤醒后继节点
         doReleaseShared();
         return true;
     }
     return false;
 }   

在CountDownLatch中通过countDown来减少倒计时数,这是最重要的一个方法,我们继续跟进源码看到它通过releaseShared()方法去释放锁,这个方法是AQS内部的默认实现方法,而在这个方法中再一次的调用了tryReleaseShared(arg),这是一个AQS的钩子方法,方法内部仅有默认的异常处理,真正的实现由CountDownLatch内部类Sync完成,如下

// 对 state 进行递减,直到 state 变成 0;
// 只有 count 递减到 0 时,countDown 才会返回 true
protected boolean tryReleaseShared(int releases) {
    // 自选检查 state 是否为 0
    for (;;) {
        int c = getState();
        // 如果 state 已经是 0 了,直接返回 false
        if (c == 0)
            return false;
        // 对 state 进行递减
        int nextc = c-1;
        // CAS 操作更新 state 的值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

当这个tryReleaseShared函数返回true时,也就是state扣减到了零,就会调用doReleaseShared唤醒CLH队列中阻塞等待的线程

3.2. await()方法

除了countDown()方法外,在CountDownLatch中还有一个重要方法就是await,在多线程环境下,线程的执行顺序并不一致,因此,对于一个倒时器也说,先开始的线程应该阻塞等待直至最后一个线程执行完成,而实现这一效果的就是await()方法!

// 等待
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
// 带有超时时间的等待
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

其中await()方法可以配置带有时间参数的,表示最大阻塞时间,当调用 await() 的时候,我们会调用aqs的一个模板方法acquireSharedInterruptibly(arg),如下:

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

 Thread.interrupted()为判断线程书否为中断状态,如果为中断状态,抛出中断异常,否则会调用tryAcquireShared(arg)方法,tryAcquireShared方法为AQS的钩子函数,由静态内部类Snyc实现,如下

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

也就是说,当前state = 0就返回1,也就不会执行doAcquireSharedInterruptibly(arg),直接放行没有堵塞。否则会执行doAcquireSharedInterruptibly(arg)这个方法,这个方法内部主要是将当前线程加入CLH队列阻塞等待。

4. CountDownLatch的基本使用

由于await的实现步骤和countDown类似,我们就不贴源码了,大家自己跟进去也很容易看明白,我们现在直接来一个小demo感受一下如何使用CountDownLatch做一个倒时器

public class Test {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个倒计数为 3 的 CountDownLatch
        CountDownLatch latch = new CountDownLatch(3);

        Thread service1 = new Thread(new Service("3", 1000, latch));
        Thread service2 = new Thread(new Service("2", 2000, latch));
        Thread service3 = new Thread(new Service("1", 3000, latch));

        service1.start();
        service2.start();
        service3.start();

        // 等待所有服务初始化完成
        latch.await();
        System.out.println("发射");
    }

    static class Service implements Runnable {
        private final String name;
        private final int timeToStart;
        private final CountDownLatch latch;

        public Service(String name, int timeToStart, CountDownLatch latch) {
            this.name = name;
            this.timeToStart = timeToStart;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(timeToStart);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name);
            // 减少倒计数
            latch.countDown();
        }
    }
}

输出结果

3
2
1
发射

 执行结果体现出了倒计时的效果每隔1秒进行3,2,1的倒数;其实除了倒计时器外CountDownLatch还有另外一个使用场景:实现多个线程开始执行任务的最大并行性

多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。

具体做法是: 初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 (new CountDownLatch(1)),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    System.out.println("5位运动员就位!");
                    //等待发令枪响
                    countDownLatch.await();

                    System.out.println(Thread.currentThread().getName() + "起跑!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        // 裁判准备发令
        Thread.sleep(2000);
        //发令枪响
        countDownLatch.countDown();
    }
}

 输出结果

5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
Thread-0起跑!
Thread-3起跑!
Thread-4起跑!
Thread-1起跑!
Thread-2起跑!

5. 总结

CountDownLatch 是一个多线程同步辅助类,它允许一个或多个线程等待一系列操作在其他线程中完成。这个机制类似于一场赛跑,选手们在起跑线准备,等待发令枪响后才能开始比赛。在 CountDownLatch 的场景中,线程们等待一个共同的信号,只有当计数器降至零时,它们才能继续执行。

CountDownLatch 提供了两个主要方法:countDown() 和 await()。countDown()方法用于将计数器减一,而 await() 方法会阻塞调用线程,直到计数器达到零。这种机制确保了所有线程都会等待必要的操作完成。

内部实现上,CountDownLatch 通过一个静态内部类 Sync 继承自 AbstractQueuedSynchronizer(AQS)。AQS 提供了一个框架,用于构建自定义的同步器。在 CountDownLatch 中,Sync 类通过重写 AQS 的钩子方法 tryReleaseShared() 和 tryAcquireShared() 来实现其同步机制。

  • tryReleaseShared() 方法用于在共享模式下尝试释放资源
  • tryAcquireShared() 方法用于在共享模式下尝试获取资源

我们可以发现,几乎所有基于AQS构造的同步类,实现原理都是差不多的,都是通过维护AQS中被volatile修饰的state变量作为竞态条件来实现线程同步。

CountDownLatch倒计时器源码解读与使用,Java源码解读,java,开发语言文章来源地址https://www.toymoban.com/news/detail-855728.html

到了这里,关于CountDownLatch倒计时器源码解读与使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue-element-table表格实现每一条数据绑定定时器实现倒计时或者持续时间

    前言 最近在开发中遇到一个需求,每一条数据前端计算处理时间,或者是倒计时时间。 第一表格不可能展示所有数据,所以我们需要当前时间和数据库开始时间获取一个初始值。 第二我们需要把定时器持续时间绑给每一条数据中,方便展示,和操作时候传递给后端存储。

    2024年01月20日
    浏览(57)
  • 北邮22级信通院数电:Verilog-FPGA(11)第十一周实验(2)设计一个24秒倒计时器

    北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章,请访问专栏: 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 一.代码部分 1.1  counter_24.v 1.2  divide.v 1.3  debounce.v 二.管脚分配 三

    2024年02月04日
    浏览(43)
  • 倒计时 1 天!浙江大学、乘云数字、阿里云等多位技术专家教授解读最新运维技术

    莫听监控繁杂声,何妨观测且徐行; 智能诊断快胜马,一键运维定乾坤。 各位朋友们,距离系统运维 MeetUp 开始只剩 2 天!此次 MeetUp 将围绕 Linux 的应用和系统可观测,eBPF 跟踪以及诊断技术等系统运维热门话题进行,活动上也将重磅发布“龙蜥一站式自动化运维 SysOM 3.0”,

    2024年02月13日
    浏览(36)
  • HTML数字倒计时效果附源码

    HTML页面代码

    2024年02月10日
    浏览(34)
  • 性能优化实战使用CountDownLatch

    1.分析问题 原程序是分页查询EventAffinityScoreDO表的数据,每次获取2000条在一个个遍历去更新EventAffinityScoreDO表的数据。但是这样耗时比较慢,测试过30万的数据需要2小时 单个线程一个个遍历去更新表数据太慢了,我想把2000的数据分成多份,每份200条,可以分成10份。每份用一

    2024年02月07日
    浏览(33)
  • python实现元旦多种炫酷高级倒计时_附源码【第20篇—python过元旦】

    这是一个简单的Python脚本,用于计算离指定日期(2024年元旦)的倒计时,以天、小时、分钟和秒为单位。 让我逐步解释代码: 导入所需的功能模块: 这些模块提供了日期时间处理、系统输入/输出、数学运算和时间操作的功能。 定义新的一年日期: 在这里,创建了一个 d

    2024年02月03日
    浏览(51)
  • ListenableFuture和countdownlatch使用example

    ListenableFuture可以允许你注册回调方法(callbacks),在运算(多线程执行)完成的时候进行调用, 或者在运算(多线程执行)完成后立即执行

    2024年02月07日
    浏览(43)
  • Flask_Login使用与源码解读

    用户登录后,验证状态需要记录在会话中,这样浏览不同页面时才能记住这个状态,Flask_Login是Flask的扩展,专门用于管理用户身份验证系统中的验证状态。 注:Flask是一个微框架,仅提供包含基本服务的核心(路由、网关接口、模板),其它功能都是通过扩展来实现,它有无

    2024年02月06日
    浏览(39)
  • 分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

    2022年11月23日首发于掘金,现在同步到公众号。 11. 前言 大家好,我是若川。推荐点右上方蓝字若川视野把我的公众号 设为星标 。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》

    2024年02月05日
    浏览(97)
  • Java并发工具CountDownLatch的使用和原理

    CountDownLatch 允许一个或多个线程等待其他线程完成操作。 假如有这样一个需求:我们需要解析一个 Excel 里多个 sheet 的数据,此时可以考虑使用多线程,每个线程解析一个 sheet 里的数据,等到所有的 sheet 都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程

    2024年02月16日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包