CountDownLatch源码分析

1.CountDownLatch的用法

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

2.countDown源码

 

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

CountDownLatch源码分析

 

CountDownLatch源码分析

 

CountDownLatch源码分析

 

CountDownLatch源码分析

 

    Sync的继承关系如下所示

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

 

3.await源码

 

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

 

CountDownLatch源码分析

 

    acquireSharedInterruptibly方法具体内容如下。

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

 

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

 

CountDownLatch源码分析

有什么问题,扫码关注我的微信公众号第一时间联系到我。同时可以第一时间收到我的原创文章

CountDownLatch源码分析