Java中的CountDownLatch源码解析

 更新时间:2023年12月22日 09:59:06   作者:正经人z.  
这篇文章主要介绍了Java中的CountDownLatch源码解析,CountDownLatch类是一个同步辅助装置,允许一个或多个线程去等待直到另外的线程完成了一组操作,需要的朋友可以参考下

一、简介

1、CountDownLatch类是一个同步辅助装置,允许一个或多个线程去等待直到另外的线程完成了一组操作。

2、它通过count进行初始化,await方法会阻塞直到当前的count为0由于调用了countDown方法,之后所有的线程将被释放并且立即返回结果。count不能被重置,如果你想count可以重置,请使用CyclicBarrier

3、CountDownLatch是一个通用的同步工具,可用于多种用途。CountDownLatch初始化使用count作为一个简单的可开可关的大门:所有的线程调用await方法等待在大门里,当一个线程调用了countDown方法后大门打开

二、源码分析

 public class CountDownLatch {
        //内部类Sync继承了AQS
        private static final class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = 4982264981922014374L;
            //构造方法中的count其实就是传给了AQS的state属性
            Sync(int count) {
                setState(count);
            }
            //得到的AQS的state属性值
            int getCount() {
                return getState();
            }
            //重写的AQS的tryAcquireShared,在共享模式的情况下获取锁
            protected int tryAcquireShared(int acquires) {
                //获取锁的前提是state为0,表示当前未被其他线程占有
                return (getState() == 0) ? 1 : -1;
            }
            //重写的AQS的tryReleaseShared,在共享模式下释放锁
            protected boolean tryReleaseShared(int releases) {
                // 减count; 当count为0时唤醒
                for (;;) {
                    int c = getState();
                    if (c == 0)  //表示释放锁的前提是占有锁,也就是state的属性值大于0
                        return false;
                    int nextc = c-1;  //state的值减1
                    if (compareAndSetState(c, nextc))  //利用CAS来改变state的值
                        return nextc == 0;  //当state的值为0时返回true
                }
            }
        }
        private final Sync sync;
        /**
         * 通过count初始化一个CountDownLatch
         *
         *在线程调用await方法后如果想通过,必须执行count次countDown方法
         * @如果count为负数,则抛出IllegalArgumentException
         */
        public CountDownLatch(int count) {
            if (count < 0) throw new IllegalArgumentException("count < 0");
            this.sync = new Sync(count);
        }
    ================================================================================================
    				两个核心方法,其实底层使用的AQS的方法(共享模式下)
        /**
         * 调用await方法会使当前的线程等待除非count的数值降为0或者中间抛出异常
         *
         * 如果当前的count为0,则该方法立即返回
         *
         *如果当前的count大于0,则当前线程因线程调度目的而被禁用,并且休眠,直到发生以下两种情况之一:
         *1、调用countDown方法将count的值降为0
         *2、其他的线程打断了当前线程,使用Thread.interrupted
         */
        public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
        /**
         *减count的值,如果count的值为0了,则释放所有等待的线程
         *
         *如果当前的count值大于0,那么它是被减了。
         *如果当前的count值等于0,那么所有等待的线程被唤醒接受线程的调度
         */
        public void countDown() {
            sync.releaseShared(1);
        }
    ================================================================================================
        /**
    	 *返回count的值,用于调试或者测试
         */
        public long getCount() {
            return sync.getCount();
        }

三、小练习-模拟王者荣耀单挑

public static void main(String[] args) throws Exception{
        CountDownLatch cd = new CountDownLatch(3);
        Thread beginGame = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    cd.await();
                    System.out.println("欢迎来到王者荣耀,敌军还有30秒到达战场============");
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        },"beginGame");
        Thread player1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    System.out.println("玩家一以准备=======");
                    cd.countDown();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        },"player1");
        Thread player2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    System.out.println("玩家二以准备=======");
                    cd.countDown();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        },"player2");
        beginGame.start();
        player1.start();
        player2.start();
    }

四、总结

1、请注意,只有在共享模式下才能使用CountDownLatch,因为只有在共享模式下,AQS的state属性的值才有可能大于1,才有后续的等待state的值为0,其他的线程才能被唤醒继续执行的可能,这里针对的多个线程等待!

2、CountDownLatch里面有两个核心方法:await和countDown。这里注意,await和平时学习的Condition中的await不一样,这里的await就是线程获取锁且必须在state值为0,也就是该资源未被占有的情况下,获取锁成功后state的值从0变为1,表示该线程持有了该资源的锁。countDown就是将state的属性值减1。这两个方法其实都是调用的AQS的方法执行的。

3、初始化传入的count表示的就是AQS的state属性的值,可以理解为持有该资源的线程数,其他的线程想要拿到这个资源,必须等到该资源的state的值变为0才能被唤醒去获取锁。也说明了如果该state的值要从N变为0,需要执行N次countDown方法

4、有点类似于线程通信机制的wait/notify。

5、CountDownLatch典型的用法是将一个程序分为n个互相独立的可解决任务,并创建值为n的CountDownLatch。当每一个任务完成时,都会在这个锁存器上调用countDown,等待问题被解决的任务调用这个锁存器的await,将他们自己拦住,直至锁存器计数结束。

6、个人理解:线程A和线程B争夺一个资源,且线程B需要在线程A之后才能执行,线程A首先拿到这个资源,然后线程A需要执行一个大任务,这个大任务可以分解为N个小任务,所以,创建一个N的count的CountDownLatch,所以,state的值就是N了,线程A每执行完一个小任务后,将调用CountDownLatch的countDown方法将state的值减1,直到最后,所有的小任务执行完毕,代表线程A的大任务也就执行完毕了,此时,该资源的state的值为0了,代表可以被其他线程争夺获取锁。线程B就可以调用countDown的await就能够获取该资源的锁,并将state的值变为1

到此这篇关于Java中的CountDownLatch源码解析的文章就介绍到这了,更多相关CountDownLatch源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 23种设计模式(1) java单例模式

    23种设计模式(1) java单例模式

    这篇文章主要为大家详细介绍了23种设计模式之java单例模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • java实现Composite组合模式的实例代码

    java实现Composite组合模式的实例代码

    这篇文章主要介绍了java实现Composite组合模式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Java中的Semaphore信号量使用方法代码实例

    Java中的Semaphore信号量使用方法代码实例

    这篇文章主要介绍了Java中的Semaphore信号量使用方法代码实例,Semaphore是一种基于计数的信号量,它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞,需要的朋友可以参考下
    2023-11-11
  • 一文带你搞懂Java中的递归

    一文带你搞懂Java中的递归

    这篇文章主要为大家详细介绍了Java中的递归的实现以及应用,文中的示例代码讲解详细,对我们学习Java有一定帮助,需要的可以参考一下
    2022-10-10
  • SpringCloud无法获取Nacos中的配置文件信息解决方案

    SpringCloud无法获取Nacos中的配置文件信息解决方案

    文章介绍了在项目启动时解决@Value占位符值找不到问题的步骤,包括检查版本号、添加依赖、检查配置文件优先级以及正确配置dataID
    2025-12-12
  • 详解Flutter TabLayout 布局用法

    详解Flutter TabLayout 布局用法

    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。这篇文章主要介绍了Flutter TabLayout 布局用法,需要的朋友可以参考下
    2019-07-07
  • Java命名规范

    Java命名规范

    本文主要介绍了Java命名规范。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • MyBatis插入时获取自增主键方法

    MyBatis插入时获取自增主键方法

    MyBatis 3.2.6插入时候获取自增主键方法有两种,下面以以MySQL5.5为例通过两种方法给大家介绍mybatis获取自增主键的方法,一起看看吧
    2016-11-11
  • java 容器的快速失败(fast-fail)机制

    java 容器的快速失败(fast-fail)机制

    Java容器的快速失败机制是一种在迭代过程中检测并处理集合并发修改的特性,该机制适用于ArrayList、HashMap等集合类,本文就来介绍一下java 容器的快速失败(fast-fail)机制,感兴趣的可以了解一下
    2024-11-11
  • 使用Lombok导致打印的tostring中缺少父类的属性问题

    使用Lombok导致打印的tostring中缺少父类的属性问题

    使用Lombok时,若发现@Data注解的@ToString不包含父类属性,可通过添加@ToString(callSuper=true)解决,此方法确保在生成toString()时包括父类的属性,有效解决只打印子类属性的问题,这种做法对于需要完整信息展示的场景尤为重要
    2024-11-11

最新评论