- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
关注 王有志 ,一个分享硬核Java技术的互金摸鱼侠 欢迎你加入 Java人的提桶跑路群 : 共同富裕的Java人 。
今天我们来聊一聊AQS家族中的另一个重要成员CountDownLatch。关于CountDownLatch的面试题并不多,除了问“是什么”和“如何实现的“外,CountDownLatch还会和CyclicBarrier进行对比:
什么是CountDownLatch?它是如何实现的?
CountDownLatch和CyclicBarrier有什么区别?
按照惯例,我们依旧是按照“是什么”,“怎么用”和“如何实现的”这3步来分析CountDownLatch,至于与CyclicBarrier的差异,下一篇我们再详细分析.
Tips :今天的“是什么”和“怎么用”合并了.
不知道你有没有参加过那种感动老板,并伴以“提升”组织凝聚力为主旨的公司团建?通常行政会组织一场越野徒步活动,规定每个人都到达终点后才能吃饭,美名其曰“不抛弃不放弃的团队精神”。而老板会早早的在终点拿着花名册等待,当员工到达终点后,在花名册上划掉自己的名字,当最后一名员工到达终点后,还要敲响锣鼓,告知老板可以开始下一轮的折磨了.
那么这样一场越野徒步活动就可以用CountDownLatch来进行简单的代码描述:
CountDownLatch countDownLatch = new CountDownLatch(10);
// 10个人进行越野徒步
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
// 每个人比前一个选手晚1秒
TimeUnit.SECONDS.sleep((finalI + 1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("选手[" + finalI + "]到达终点!!!");
countDownLatch.countDown();
}).start();
}
// 老板在目的地吃瓜,等待每个选手到达
countDownLatch.await();
// 开饭啦!
System.out.println("老板说:所有人都到齐了,午饭是每人一个吐司!!!");
看到这里,参加过此类团建活动的小伙伴是不是血压有些高了?但是你先别高,因为在这样一场血压飙升的团建中,我们已经不知不觉的掌握了CountDownLatch的用法了.
我们先试着从名字来理解CountDownLatch,CountDownLatch是一个组合词,CountDown译为“倒计时”,Latch译为“门闩”,结合起来就是倒计时结束后打开门闩(进行后续的动作)。再来看Doug Lea是如何解释CountDownLatch的作用的:
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.await 就可以实现多个线程的等待.
先来看作为AQS家族的成员,CountDownLatch是如何与AQS产生联系的:
很熟悉的结构,与ReentrantLock和Semaphore一样,都是内部的同步器类 Sync 继承了AQS,但不同的是CountDownLatch中的 Sync 不再是抽象类.
既然是继承自AQS,并且内部有计数器(倒计数也是计数)的使用,那么我们就再次搬出《 AQS的今生,构建出JUC的基础 》中那段关于同步状态作为计数器特性的说明:
AQS中, state不仅用作表示同步状态,也是某些同步器实现的计数器 ,如: Semaphore 中允许通过的线程数量, ReentrantLock 中可重入特性的实现,都依赖于 state 作为计数器的特性.
虽然没有举CountDownLatch的例子,但我知道在经过Semaphore的分析后你一定能够猜到CountDownLatch是如何使用同步状态作为计数器特性的。接下来我们就一起来看一下同步状态在CountDownLatch中的应用.
通过AQS家族成员的类图可以看到,CountDownLatch中的同步器 Sync 并没有公平与非公平的区别,因此构造器只需要提供设置计数的能力即可:
public class CountDownLatch {
public CountDownLatch(int count) {
if (count < 0){
throw new IllegalArgumentException("count < 0");
}
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
protected final void setState(int newState) {
state = newState;
}
}
不出所料,CountDownLatch的计数依旧是回归到了AQS的 state 上.
回到徒步活动中,员工到达终点后,需要在花名册上划掉自己的名字,最后一名到达后还要敲响锣鼓。在代码实现中,我们使用了 CountDownLatch.countDown 表示员工到达的状态,并执行相应的动作:
public class CountDownLatch {
public void countDown() {
sync.releaseShared(1);
}
private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 获取同步状态
int c = getState();
// 同步状态为0,返回失败
if (c == 0){
return false;
}
// 计数减1,并通过CAS更新
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
// 计数器为0时返回true
return nextc == 0;
}
}
}
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
}
回忆下《 详解AQS家族的成员:Semaphore 》中 Semaphore#release 方法的实现,是不是觉得似曾相识?同样是执行 Sync#tryReleaseShared 方法,并在成功后调用AQS的 doReleaseShared 方法。区别是 Semaphore#tryReleaseShared 的实现是计数加1,而 CountDownLatch#tryReleaseShared 实现是计数减1.
我们注意另一个问题,CountDownLatch的 Sync#tryReleaseShared 方法只有在 计数器减为0时才会返回true ,此时能进入AQS的 doReleaseShared 方法,否则都只是执行了计数器减一的操作.
此外,我们也知道AQS的 doReleaseShared 方法起到了唤醒AQS等待队列中节点的作用,也就是说 只有在计数器减为0时,CountDownLatch才会执行一次唤醒工作 .
Tips :AQS的 doReleaseShared 已经在《 详解AQS家族的成员:Semaphore 》中分析过了,就不再赘述了~~ 。
我们知道老板一早就乘车到达了终点等待,那么老板是如何判断自己要等待呢?老板提前抵达终点后,拿出花名册统计到达人数,当发现还有人没有到达终点时,他就准备打个盹,睡一觉.
我们使用了 CountDownLatch.await 表示老板进入等待状态:
public class CountDownLatch {
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
}
是不是还是很眼熟?与Semaphore一样使用了AQS的 acquireSharedInterruptibly 方法,那我们重点关注CountDownLatch的 Sync#tryAcquireShared 方法:
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
protected int tryAcquireShared(int acquires) {
// 同步状态为0返回1,不为0返回-1
return (getState() == 0) ? 1 : -1;
}
}
}
该方法对同步状态做出了判断,结合AQS的 acquireSharedInterruptibly 方法我们可以得到以下结论:
当同步状态等于0时, tryAcquireShared 返回1,不执行 doAcquireSharedInterruptibly ,即执行了足够次数的 countDownLatch#countDown ,无需进入等待队列; 。
当同步状态不等于0时, tryAcquireShared 返回-1,执行 doAcquireSharedInterruptibly ,即尚未执行足够次数的 countDownLatch#countDown ,需要进入等待队列.
简单来说就是在调用 CountDownLatch#await 方法时 计数器不为0构建等待队列,为0就什么也不执行.
Tips :AQS的 doAcquireSharedInterruptibly 已经在《 详解AQS家族的成员:Semaphore 》中分析过了,就不再赘述了~~ 。
关于CountDownLatch的内容到这里就结束了,内容并不多。当我们不熟悉AQS的时候,不认识CountDownLatch的时候,会觉得CountDownLatch是一种“挺高级”的工具,但当我们深入其中时就会发现,“高级”的技术其实并不难学.
好了,如果本文对你有帮助的话,还希望你不要吝啬点赞。最后欢迎大家关注分享硬核技术的金融摸鱼侠 王有志 ,以及关注我的专栏《 Java面试都问啥? 》,我们下次再见! 。
最后此篇关于19.详解AQS家族的成员:CountDownLatch的文章就讲到这里了,如果你想了解更多关于19.详解AQS家族的成员:CountDownLatch的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要在 php 和 MySQL 中构建家谱。我对那里缺乏开源可定制的 html 家谱构建软件感到非常惊讶,但我离题了。我花了很多时间阅读有关存储 MySQL 有向图和家谱的内容。一切对我来说都有意义
我尝试将数据添加到数据库。但是有这个错误。 Illuminate \ Database \ Eloquent \ MassAssignmentException fam 'fam' 是名称优先参数。
在 lisp 家族(编辑:lisp-1)语言中是否有一种方法可以根据其作为函数或参数的位置来区分符号求值(即在求值时重写该符号的求值) ? 作为一个例子(我不需要这个功能,这是一个例子),我想在一组对
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 7 年前。 Improve this qu
我是一名优秀的程序员,十分优秀!