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源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 简单实现Java通讯录系统

    简单实现Java通讯录系统

    这篇文章主要为大家详细介绍了如何简单实现Java通讯录系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • MyBatis核心配置文件深入分析

    MyBatis核心配置文件深入分析

    这篇文章主要介绍了MyBatis核心配置文件,MyBatis的前身就是iBatis,iBatis本是由Clinton Begin开发,后来捐给Apache基金会,成立了iBatis开源项目。2010年5月该项目由Apahce基金会迁移到了Google Code,并且改名为MyBatis
    2022-12-12
  • mybatis xml文件热加载实现示例详解

    mybatis xml文件热加载实现示例详解

    这篇文章主要为大家介绍了mybatis xml文件热加载实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Java 编程中十个处理异常的建议

    Java 编程中十个处理异常的建议

    这篇文章主要介绍了Java 编程中十个处理异常的建议,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • java操作ftp下载文件示例

    java操作ftp下载文件示例

    这篇文章主要介绍了java操作ftp下载文件的示例,需要的朋友可以参考下
    2014-02-02
  • Java复杂链表的复制详解

    Java复杂链表的复制详解

    复杂链表指的是一个链表有若干个结点,每个结点有一个数据域用于存放数据,还有两个指针域,其中一个指向下一个节点,还有一个随机指向当前复杂链表中的任意一个节点或者是一个空结点,我们来探究一下在Java中复杂链表的复制
    2022-01-01
  • 使用Filter实现登录权限验证

    使用Filter实现登录权限验证

    这篇文章主要为大家详细介绍了使用Filter实现登录权限验证,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • Java jar打包工具使用方法步骤解析

    Java jar打包工具使用方法步骤解析

    这篇文章主要介绍了Java jar打包工具使用方法步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • 带你快速搞定java多线程(3)

    带你快速搞定java多线程(3)

    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下
    2021-07-07
  • 详解Java8的forEach(...)如何提供index值

    详解Java8的forEach(...)如何提供index值

    这篇文章主要介绍了详解Java8的forEach(...)如何提供index值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03

最新评论