透过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以及对应的锁”
AQS条件队列和同步队列的关系
透过ReentrantReadWriteLock窥探AQS
通过Semaphore窥探AQS
尾声
tips:文章中的部分图片是传到微博上再导过来的(微信文章中的不能直接用),有点糊,如果想要高清的图片可以关注我的公众号Alitaba119,公众号文章中的图片比较清楚。
如果本文章解决了各位看官心里的一些疑惑,可以关注本公众号,后续会有更多精彩文章,也可以帮忙推荐给其他的小伙伴,让更多的人受益,万分感谢。
如果看了文章之后心中还有疑惑,请在文章下面留言,下一篇文章「AQS条件队列和同步队列的关系」中我会对前一篇文章大家的问题做一个解答。
爱情终将消失于茫茫的时间洪流之中,沉淀于厚重的黄泥沙丘之下...