多线程AQS系列1 - CountDownLatch原理解读

- 扯皮

-为什么需要研究CountDownLatch,因为在java多线程的实际应用场景中确实需要CountDownLatch这个小工具.
CountDownLatch用法比较简单!但是它咋就能做到,让一些线程做到高风亮节,等着另一些线程执行完自己再执行呢?背后的原理是什么呢?带着问题继续往下来

- 模拟业务场景

有一个生产库,每小时生产100万条业务数据,要求是每到整点计算完前一小时的全部业务数据。整体计算时长控制务必1小时内。现业务要求整体任务分为2个,A任务是根据前一小时的原业务数据计算出中间结果数据,B任务是待A任务全部结束后再根据中间结果数据再计算出最终结果。
现设计了2个线程池A和B各100个线程,假设A池的任务都是A任务,B池的任务都是B任务。
主要问题:一定是A任务结束后,B任务才能开始。否则B只能等待!
这也正是CountDownLatch应用场景。

- 查看CountDownLatch源码 一路坎坷

我最近的学习CountDownLatch的路也是一波三折啊,最早的打算是既然想深入了解CountDownLatch背后的原理,它又是AQS同步框架开发的,那就直接看AQS源码好了,打开源码后看了看,我发现我错了,根本看不懂!直接看源码行不通,断点啥的也进不去,那好,新建个类AQS,把源码复制粘贴到AQS里,然后AQS搞的干净点,因为英文不好是硬伤,注释占用几百行看着真懵逼。再建个类MyCountDownLatch,把CountDownLath源码也复制进来。这俩类都先这么搞定再说。
顺利编译完成,写了个Demo类,就是CountDownLatch如何使用的简单例子,在此不多说,运行报错,问题出在AQS的Unsafe类上。这个类就更牛逼了(以后详谈)。
最后又新建了个类算是解决了(不详说,亲自上手+度娘就知道了)。
自己的CountDownLatch + 自己的AQS 运行起来了。
但是,到目前为止,对于理解CountDownLatch和AQS原理 还正式开始…

- 解读准备

一个简易的CountDownLatch的使用的Demo,调用自己搭建起来的MyCountDownLatch和AQS,还有一个特殊处理类(遇到错就知道了)。代码运行起来可以是一个main线程等待多个线程(上述的模拟场景是多个线程等待多个线程)。但是看源码的过程时时刻刻想着这里都是多线程在执行这里。很重要!!!

- A任务 解读

A线程池的100个线程开始干活了…
这个是CountDownLatch的用法,ZryCountDownLatch cdl= new ZryCountDownLatch(100);
a):.cdl.countDown();一定是100个线程中的某个完成了自己的任务后,才执行的这个方法。
b):AQS的 releaseShared()方法(包含一个设计模式,模板方法模式),总之会调用子类的方法,CountDownLatch里实现的 tryReleaseShared()。
c):此时100个线程都在这个方法里进行死循环,然后进行CAS操作,直到CAS操作成功,也就是state-1 操作成功!
d): 成功的线程可以回线程池了。不成功的继续循环,继续CAS操作。直到最后一个线程执行完后,state=0 了,最后一个线程中奖了, 暂时还不能回线程池,有个更艰巨的任务,需要最后一个线程去唤醒在队列里等待的B里的100个线程。

- B任务解读

A和B都是同时进行,假设A任务比较耗时,肯定会B等待A的情况。
a):B的100个线程同时执行cdl.await();这个方法。注意:cdl这个对象始终是一个。
b): 执行到sync.acquireSharedInterruptibly(1);首先tryAcquireShared(),尝试获取state的值,如果state=0,表示A的100个线程都已经结束了,我们B的可以直接执行自己的任务不用等了。
c):如果state>0,哎,还有A线程池的线程还在执行任务,所有B的线程还不能去执行B自己的任务。doAcquireSharedInterruptibly(),B的所有线程先别执行自己的任务了,先统一走这个方法吧 。
d):B线程池的100个线程咔咔进来了。
构建了一个队列,这100个线程逐渐都放到这个队列里了。100个线程也是类似于A的循环+cas操作state,这里的B利用cas操作的是Node对象。
e):一次循环也只能挂一个线程到队列的尾部。最后100个线程都是阻塞的了,只能等待被唤醒了,且全部挂到队列上了。
(挂节点这里还有很多细节的东西,大体思路先这么理解着,学物理时候不是讲究先整体分析再局部分析嘛,先对整体思路有一个认识,然后看细节就爽多了)
f):到此,B组的100个线程都在队里里消停待着了…等着被唤醒。

- 唤醒员工作了

a):A线程池选中的唤醒员去看望那些在队列里一直等待的所有的B池的兄弟们。
b):好久过后了,A线程池的线程们努力完成了各自的任务,然后去state-1操作,突然最后一个线程完成任务后,去state-1时,发现state=0了。我被选中当做唤醒员了。所以这个线程就咔咔跑到了B线程池所有线程所在的队列里了。从头喊着:起来了B线程池的第一个线程兄弟,我们A线程池的兄弟都完成任务回线程池休息去了都。。。我是最后打酱油的了。。。听说你们B线程池都还有有新任务啊 !循环一个一个唤醒,直到全部唤醒完。。
c):所有唤醒后的B线程池的线程就可以去继续执行B线程池自己的任务了。
d) 最后:
A线程池的这个唤醒员,唤醒任务结束后,回归A线程池的大队伍喽…
- 结束语
以上是整体的一个思路梳理。先看着代码把这个整体思路琢磨透!
先整体 后局部…
这里都是整体的,没有具体的细节…
能有一个大概认识,然后再看细节代码会容易的多。

  • 共享锁的概念

只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒AQS队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能。
白话就是 唤醒时从头到尾全部唤醒喽。

新人初来匝道 还请多多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值