透过CountDownLatch窥探AQS

本文来自公众号“Kahuna”,可搜索Alitaba119,欢迎关注,转载请注明出处,非常感谢


 A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

这是CountDownLatch这个类要解决的问题:实现一个同步器,让一个或者多个线程一直等待,直到一组在其他线程中执行的操作完成。


目录

一、CountDownLatch类工作流程

二、CountDownLatch类用法

三、CountDownLatch类源码解析

四、CountDownLatch类的思考点

五、CountDownLatch类的适用场景

六、文章透过ReentrantLock窥探AQS问题回答

七、AQS系列推荐


一、CountDownLatch类用法

先看下我们的测试代码,waitThread1和waitThread2两个线程等待latch,另外5个任务线程执行到latch.countDown(),任务就绪(此时任务线程会继续执行后面的逻辑),等最后一个任务执行latch.countDown()之后waitThread1,waitThread2被唤醒继续执行。

下图是两次执行结果:


从上图结果中可以看出来,waitThread1和waitThread2两个线程等待5个任务线程就绪之后才继续执行;另外waitThread1和waitThread2的执行和5个任务线程的执行没有固定的先后顺序,任务线程执行完countDown()方法之后会继续执行后面的逻辑。

二、CountDownLatch类工作流程

通过下面这个动图来展示CountDownLatch的整个工作流程,需要注意的是等待线程和任务线程的区别,另外在任务就绪之后,整个释放的流程需要重点关注,共享节点的释放并不是固定一个个节点按顺序释放的,而是可能多个节点同时释放的。


三、CountDownLatch类源码解析

CountDownLatch源码相对来说还是比较简单的,最核心的就是理解他的工作步骤以及同步器的实现。
工作步骤:

  • 初始化的时候定义几个任务,即同步器中state的数量
  • 等待线程执行await方法等待state变成0,等待线程会进入同步器的等待队列
  • 任务线程执行countDown方法之后,state值减1,直到减到0,唤醒等待队列中所有的等待线程。

同步器实现了tryAcquireShared方法,判断当state!=0的时候把等待线程加入到等待队列并阻塞等待线程,state=0的时候这个latch就不能够再向等待队列添加等待线程;另外实现了tryReleaseShared,判断当前任务是否是最后一个任务,当state减到0的时候就是最后一个任务,然后会以传播唤醒的方式唤醒等待队列中的所有等待线程。


四、CountDownLatch类的思考点

这边的思考点只有一个,latch任务线程都就绪之后怎么释放?要了解这个点,我们需要具体看下AQS中的doReleaseShared方法。

不知道大家有没有想过,上图1,2,3处代码,作者为什么要这么写?

在解释这三处代码之前,我们首先要弄清楚SHARD节点的释放方式。

假如我们有以下的共享节点组成的AQS等待队列:

主线程调用doReleaseShared方法,进入for循环自旋,head节点的地址赋值给h,然后判断是否有后继节点并需要释放,最后判断h中的地址和当前的head节点的地址是否一样,如果一样,则退出for循环,流程图如下:

每一个被唤醒的线程都会进入自旋逻辑,而且每个线程都有一个head地址的本地变量副本,当这个副本和当前的head节点的地址不一致的时候,会继续自旋, 获取最新的head节点,去尝试唤醒下一个线程。我们看下下面的这几种时序:

第一处代码为什么需要把waitStatus设置成0?
如果waitStatus=NODE.SIGNAL,不把状态设置成0,则自旋的时候有可能重复触发唤醒同一个节点(上图中如果主线程,t1线程,t2线程自旋的时候,head指向线程t3节点,waitStatus如果等于-1,则线程t4就会被重复唤醒)。

第二处代码:为什么等于0的时候要设置NODE.PROPAGATE?
还是拿上面的case举例子,假如现在head指向线程t4,如果主线程,t1线程,t2线程,t3线程同时完成自己的唤醒任务,然后都获取了线程t4的head地址,第一个线程把线程t4节点的waitStatus改成了0,那后面的线程会尝试把0改成PROPAGATE(这里过来的线程有两种可能,修改waitStatus从-1到0失败继续自旋过来,或者是判断waitStatus=0过来),如果不成功,则自旋,如果成功则判断head是否有变化,简单的说就是让竞争修改waitStatus为PROPAGATE失败的线程继续释放其他的节点。

第三处代码:为什么要设置h==head退出自旋?

最主要的原因是,不然我怎么跳出自旋呢!另外如果head不一致,当前线程可以帮忙一起释放剩余的等待线程,提高释放效率。


五、CountDownLatch类的适用场景

如果你要执行一个任务,但是必须先等其他几个任务做到某个程度这个任务才能够启动,这个时候就比较适合用CountDownLatch。但是一般会使用超时等待的方式来处理,不然如果其中某个任务异常没有完成,或者超时了,那么任务会一直等待在那里。


六、文章透过ReentrantLock窥探AQS问题回答

目前ReentrantLock窥探AQS文章还没收集到问题,有兴趣的同学可以点进去看看,在文章下留言就行


七、AQS系列推荐

“ 本系列文章旨在对并发包中的AQS以及跟AQS相关的锁做一个深入的讲解,让各位看官可以深入理解AQS以及对应的锁”

透过ReentrantLock窥探AQS

透过CountDownLatch窥探AQS

AQS条件队列和同步队列的关系

透过ReentrantReadWriteLock窥探AQS

通过Semaphore窥探AQS

 


尾声


tips:文章中的部分图片是传到微博上再导过来的(微信文章中的不能直接用),有点糊,如果想要高清的图片可以关注我的公众号Alitaba119,公众号文章中的图片比较清楚。

如果本文章解决了各位看官心里的一些疑惑,可以关注本公众号,后续会有更多精彩文章,也可以帮忙推荐给其他的小伙伴,让更多的人受益,万分感谢。

如果看了文章之后心中还有疑惑,请在文章下面留言,下一篇文章「AQS条件队列和同步队列的关系」中我会对前一篇文章大家的问题做一个解答。

posted on 2019-09-15 11:45  Kahuna  阅读(245)  评论(0编辑  收藏  举报

导航