专栏首页大猫的Java笔记CountDownLatch源码分析

CountDownLatch源码分析

1.CountDownLatch的用法

CountDownLatch源码分析之前,首先看一看CountDownLatch的用法,我们通过一段代码来说明CountDownLatch的基本用法,代码如下。

CountDownLatch可以指定一个count,例如我们代码中指定为10,然后启动了10个线程,线程就是执行CountDownLatch的countDown方法,每执行一次,count就会减1,直到为0的时候下面countDown的await方法才会释放主线程,并打印线程运行结束。所以整段代码的意思就是启动main以后主线程进行阻塞,直到执行指定次数CountDownLatch的countDown方法主线程才会释放,释放主线程取决于构造函数中传递的count。

2.countDown源码

.

首先看一下CountDownLatch的构造方法传递的count究竟去了哪儿,通过debug我们可以看到进入构造函数后实际上创建了一个内部类为Sync,最终count给了volatile修饰的state变量。

Sync的继承关系如下所示

回到正题我们继续查看CountDown方法的源码,CountDown方法调用releaseShared方法。

releaseShared方法中我们先查看tryReleaseShared方法的具体实现。

tryReleaseShared方法的进入后就是一个fori的死循环,只有state为值为0或者更改state的值后才能退出,首先拿到state的值,而state的值就是之前我们所说的conut,然后判断state是否为0,为0返回false,否则将state的值减1然后再通过cas的操作进行更改state的值,更改成功后如果更改后的值为0返回true,否则为false。也就是说只有state减到为0的时候才会执行tryReleaseShared中的doReleaseShared方法。

我们进入doReleaseShared方法后看到了一个熟悉的Node,没错就是那个男人,他就是一个双向链表,我们可以看到他有一个上一个node的变量prev和下一个node的变量next。此时这个node存放的就是等待的线程和上一ReentrantLock一样,如果node的头为空且头和尾相等,即node中没有等待线程那么直接break结束掉循环并结束方法。否则拿到头的状态并判断头的状态是是否为后续节点需要释放的状态,如果是使用cas操作更改头的状态为0,更改成功调用unparkSuccessor方法唤醒等待的线程。

unparkSuccessor方法唤醒节点的后继节点,拿到node的下一个节点,然后判断下一个节点是否为null或者下一个节点的状态是大于0的,那么此时就循环从尾部向前找,一直找到node中的状态为等待状态的为止。然后调用LockSupport的unpark方法唤醒线程。

到此为止CountDownLatch的countDown方法源码就结束了,我们通过流程图来总结一下。

3.await源码

首先调用内部类Sync的acquireSharedInterruptibly方法。

acquireSharedInterruptibly方法具体内容如下。

在tryAcquireShared获取到state的状态如果为0返回1,否则返回-1,即查看state是否已经为0,为0则说明已经不需要阻塞了。

doAcquireSharedInterruptibly方法实现,也就是没有为0时,需要阻塞线程的方法,首先调用addwaiter方法,即添加一个等待线程到链表中。

addWaiter实现代码如下,构造一个当前线程的node节点,然后通过cas操作将当前node节点插入到n双向链表的尾部,最后调用enq方法,而enq方法则是如果双向链表的尾部为空就初始化一个尾部节点出来,然后再通过cas操作将当前线程放到尾部上。

enq初始化双向链表的尾部,同时将当前线程节点放到尾部的后面,成为新的尾节点。

现在我们回到doAcquireSharedInterruptibly方法,刚刚我们通过addWaite方法r已经将等待线程放入到了双向链表中,然后拿到我们新添加的节点的上一个节点,如果上一个节点为头节点,此时调用tryAcquireShared方法,查看state的状态确认是否还是需要阻塞线程,如果不需要则调用setHeadAndPropagate方法。

那么setHeadAndPropagate方法具体在做什么呢?继续往下看一下,首先将自己设置为头节点,因为自己本身已经不需要进行阻塞了,同时拿到node的下一个节点,如果下一个节点不是空的那么调用doReleaseShared,此处的doReleaseShared的作用就是找到下一个需要被唤醒的节点,然后进行唤醒。

继续回到doAcquireSharedInterruptibly方法我们可以看到如果调用了setHeadAndPropagate更改了头节点那么就会将原来的头节点设置为空,这样强引用就消失了,后续就会被垃圾回收。但是如果我们此时调用tryAcquireShared获取到state不是为0,此时说明还是需要阻塞线程,那么就会执行shouldParkAfterFailedAcquire方法。shouldParkAfterFailedAcquire方法实际上就是将上一个节点的状态更改为后续节点需要进行释放的状态。最后调用parkAndCheckInterrupt方法,进行线程阻塞。

到此CountDownLatch的await方法源码分析结束,一样我们通过流程图总结一下执行流程。

本文分享自微信公众号 - 大猫的Java笔记(damaoJava),作者:大猫

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ReentrantLock的lock与unLock方法源码分析

    前面已经说过了ReentrantLock的基本用法,下面我们通过源码对ReentrantLock进行分析,首先写一个测试类,作用是在debug的时候好进行源码分...

    大猫的Java笔记
  • 肝了几天我算是理解了红黑树

    在学习红黑树之前我们需要了解一下二叉排序树,所谓二叉排序树就是一种特殊的二叉树,首先满足二叉树的性质,然后它存储数据的方式是左边节点比父节点的数据小,而右边节点...

    大猫的Java笔记
  • 堆和堆排序

    1.必须是一棵完全二叉树,完全二叉树指树的元素在新增时满足从上到下,从左到右的新增顺序。

    大猫的Java笔记
  • 源码分析— java读写锁ReentrantReadWriteLock

    今天看Jraft的时候发现了很多地方都用到了读写锁,所以心血来潮想要分析以下读写锁是怎么实现的。

    luozhiyun
  • 斜堆

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    于小勇
  • 合肥工业大学吴信东:大数据Processing Framework多层架构

    用户1737318
  • 程序员进阶之算法练习(四十七)

    题目链接 题目大意: 给出一个整数1~n的排列。 接下来有m个询问,每个询问包括 l, r, x。 (l <= x <= r) [l, r]区间内的数字...

    落影
  • H3C系列网络设备通过腾讯云兼容性验证测试

    H3C S12500-X、S6800、S5130、MSR 3600系列网络设备通过腾讯云兼容性验证测试。

    用户1379644
  • 三个关于大数据技术的问题

    大数据中,结构化数据只占 15%左右,其余的 85%都是非结构化的数据,它们大量存在于社交网络、互联网和电子商务等领域。另一方面,也许有 90%的数据来自开源数...

    加米谷大数据
  • 手把手教你破解文件密码、wifi密码、网页密码

      有时候我们在网上下载一个压缩包后,必须要关注或者支付一定费用才给你解压密码,实属比较恶心。在这里手把手叫你实现破解文件解压密码。

    RunWsh

扫码关注云+社区

领取腾讯云代金券