CountDownLatch原理简介和使用过程
前言
本文介绍下面试的高能考点 countDownLatch 的原理和应用
countDownLatch具有的功能
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
应用场景
典型的应用场景
如并行计算,当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。
原理简介
上图解释
TA线程 实例化 CountDownLatch对象并初始count为3 TA调用await 使得当前线程等待count为0 类似于看视屏的时候按了暂停键 另外一个T1线程调用了该CountDownLatch对象的countDown方法 使得count变为2 T2线程使得count变为1 T3线程使得count变为0 此时就会唤醒处于“暂停”状态的TA线程让其继续往下执行
进一步说明
CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。
CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。
调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown计数器的值减1。
当计数器值减至零时,所有因调用await()方法而处于等待状态的线程就会继续往下执行。
这种现象只会出现一次,因为计数器不能被重置,如果业务上需要一个可以重置计数次数的版本,可以考虑使用CycliBarrier
DEMO代码分析
这个示例包含了 线程池和CountDownLatch 的内容 ,更加符合实际使用场景
详细说明上述示例
针对CountDownLatch的说明
初始化一个CountDownLatch 初始值count=5
调用countDownLatch方法 count+-1即 count=count-1 每次调用count值都会减少1个
调用await方法 就是让初始化CountDownLatch的线程等待count值变为0 然后才会继续执行下面的内容
结合线程池对CountDownDatch的说明
主线程创建了一个count为5的CountDownLatch
主线程创建了一个线程池 里面有5个核心线程
往线程池中提交了5个线程并且传入countDownLatch对象到子线程中去
每一个子线程执行时会调用countDown方法将count减1
主线程调用await 等待count变为0
count变为0 主线程将继续执行
源码分析
java.util.concurrent.CountDownLatch
分析源码的过程中大家把关注点放在我截图上圈红的地方 然后咱们慢慢的一点一点的把源码看完
这里将count值设置给state变量 调用了AQS类中setState方法
这个变量被volatile修饰
保证了这个变量 具有
1、可见性
2、有序性 防止指令重排序
3、原子性
这里先简单介绍下可见性
上图解释
每一个线程都会有一个本地内存 比如图中线程A有本地内存A 该内存中保留了一份主内存中共享变量的副本
线程A对本地内存A中的共享变量的副本修改了之后 然后会立刻同步刷新到主内存中
并且会让强制缓存了该变量的线程中的数据清空
必须从主内存中重新读取最新的数据
接着说CountDownLatch源码分析
在初始化CountDownLatch的时候会实例化这个继承了AQS的内部类Sync并且将count传给AQS的state变量
countDownLatch的await方法 调用了 AQS的acquireSharedInterruptibly方法并且传入了参数1 接下来对该方法分析
『tryAcquireShared』
在共享模式下尝试获取。这个方法需要查询是否对象的状态允许在共享模式下被获取,如果允许则去获取它。
这个方法总是被线程执行获取共享锁时被调用。如果这个方法报告失败,那么获取方法可能会使线程排队等待,如果它(即,线程)还没入队的话,直到其他的线程发出释放的信号。
默认实现抛出一个“UnsupportedOperationException”
返回:
a)< 0 : 一个负数的返回表示失败;
b) 0 : 0表示在共享模式下获取锁成功,但是后续的获取共享锁将不会成功
c)> 0 : 大于0表示共享模式下获取锁成功,并且后续的获取共享锁可能也会成功,在这种情况下后续等待的线程必须检查是否有效。
看下AQS的子类Sync的tryAcquireShared方法的实现
这个逻辑过程中使用了大量的CAS来进行原子性的修改,当修改失败的时候,是会通过for(;;)来重新循环的,也就是说『doAcquireSharedInterruptibly』使用自旋锁(自旋+CAS)来保证在多线程并发的情况下,队列节点状态也是正确的以及在等待队列的正确性,最终使得当前节点要么获取共享锁成功,要么被挂起等待唤醒
我们需要一个通知信号,主要是因为当前线程要被挂起了(park)。而如果waitStatus已经是’SIGNAL’的话就无需修改,直接挂起就好,
而如果waitStatus是’CANCELLED’的话,说明prev已经被取消了,是个无效节点了,那么无需修改这个无效节点的waitStatus,而是需要先找到一个有效的prev。
因此,剩下的情况就只有当waitStatus为’0’和’PROPAGAET’了(注意,waitStatus为’CONDITION’是节点不在等待队列中,所以当下情况waitStatus不可能为’CONDITION’),这时我们需要将prev的waitStatus使用CAS的方式修改为’SIGNAL’,而且只有修改成功的情况下,当前的线程才能安全被挂起。
还值得注意的时,因此该方法的CAS操作都是没有自旋的,所以当它操作完CAS后都会返回false,在外层的方法中会使用自旋,当发现返回的是false时,会再次调用该方法,以检查保证有当前node有一个有效的prev,并且其waitStatus为’SIGNAL’,在此情况下当前的线程才会被挂起(park)。
源码分析未完待续 下篇文章继续分析
DEMO代码链接
https://gitee.com/pingfanrenbiji/myconcurrent/tree/master/src/main/java/pers/hanchao/concurrent/eg14
参考文章
https://www.jianshu.com/p/9ee0194d598c
以上是 CountDownLatch原理简介和使用过程 的全部内容, 来源链接: utcz.com/a/25333.html