CountDownLatch闭锁源码解析(基于jdk11)
CountDownLatch闭锁源码解析(基于jdk11)
1.1 CountDownLatch概述
public class CountDownLatch extends Object
CountDownLatch是一种同步工具,常被称为"闭锁",也叫做"倒计数器"。在完成一组正在其他线程中执行的操作之前,CountDownLatch允许一个或多个线程一直等待。
很明显,这类似于在开始某个行为之前的准备操作
比如有一个任务A,它要等待其他4个任务完成后才能执行后续工作,此时就可以利用CountDownLatch。
1.2 CountDownLatch原理
1.2.1 基本结构(jdk11)
UML类图可知,CountDownLatch内部同样也使用了AQS实现功能,大胆猜测与AQS里的state状态属性有关。
CountDownLatch的构造函数接受了一个int类型的count参数作为计数器,如果你想等待N个线程计数,那就传入N。通过构造函数,实际上是把count赋值给了AQS里的同步状态属性state。
1、
2、
3、
在CountDownLatch的Sync实现中,重写了tryAcquireShared和tryReleaseShared方法,此处可看出是一个共享锁。
一般情况下(常见Lock锁的实现中)我们在释放锁的时候会将state资源减少,获得锁的时候会将state资源增加,当state变为0表示释放锁成功或者没有线程获取到锁,但是CountDownLatch中state的含义则不一样:
- 在尝试获取锁的tryAcquireShared中,虽然名字叫获取锁,但事里面逻辑却只做了一个判断,如果state为0就表示获得了锁,state为其他值的情况下都没有获取锁,tryAcquireShared方法在awit()系列方法中被调用。
- 在尝试释放锁的tryReleaseShared方法中,虽然名字叫释放锁,但却仅仅是在对state尝试自减操作,它的内部是一个循环操作,每一次的调用tryReleaseShared都会首先判断state是否为0,如果是,那么返回false表示“释放锁失败”,如果不是那么尝试CAS的将state自减1,CAS成功之后会判断此时的值是否为0,如果不是那么表示“释放锁失败”,返回false,否则表示“释放锁成功”,返回true,这里的操作可以永远保证只有一个线程能够因为“释放锁成功”而返回true。tryReleaseShared方法在countDown()方法中被调用。
1.2.2 await()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
需要等待的线程调用。调用该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:
- 当计数器的值为0 时;
- 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException 异常,然后返回。
根据源码,想要调用await方法的线程能够返回,一般情况下需要获取到共享锁,而CountDownLatch内部的tryAcquireShared返回大于0的要求是state为0,即只有在state为0的时候,调用await方法的线程才能能够返回。
/**
* CountDownLatch 的await方法
*
* @throws InterruptedException 等待时被中断
*/
public void await() throws InterruptedException {
//调用了AQS 的acquireSharedInterruptibly方法,共享式可中断获取锁
sync.acquireSharedInterruptibly(1);
}
/**
* AQS 的acquireSharedInterruptibly方法
* 共享式获取同步状态,可以被中断,在AQS部分我们已经讲过了
*
* @param arg 参数,在实现的时候可以传递自己想要的数据,这里没什么用
* @throws InterruptedException 等待时被中断
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquireShared方法由AQS的子类实现,尝试共享式获取锁,如果返回值小于0,表示获取失败
if (tryAcquireShared(arg) < 0)
//获取锁失败的线程进入AQS的队列等待,在被唤醒之后还是会继续调用tryAcquireShared获取锁,直到获得锁成功
doAcquireSharedInterruptibly(arg);
}
1.2.3 await(timeout, unit)方法
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
需要等待的线程调用。调用该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:
- 当计数器值为0 时,这时候会返回true ;
- 设置的timeout 时间到了,因为超时而返回false ;
- 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException 异常,然后返回。
与空参await()方法类似,内部使用了AQS的tryAcquireSharedNanos,而空参await()则是使用acquireSharedInterruptibly
/**
* CountDownLatch 的await( timeout, unit)方法
* 超时等待
*
* @param timeout 等待时间
* @param unit 时间单位
* @return true 成功 false 失败
* @throws InterruptedException 被中断
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
//调用了AQS 的tryAcquireSharedNanos方法,共享式超时可中断获取锁
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* AQS 的tryAcquireSharedNanos方法
* 共享式超时获取锁,可以被中断,在AQS部分我们已经讲过了
*
* @param arg 参数
* @param nanosTimeout 超时时间,纳秒
* @return 是否获取锁成功
* @throws InterruptedException 被中断
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//最开始就检查一次,如果当前线程是被中断状态,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//下面是一个||运算进行短路连接的代码
//tryAcquireShared尝试获取锁,获取到了(返回大于等于0)直接返回true
//获取不到(左边表达式为false) 就执行doAcquireSharedNanos方法
//doAcquireSharedNanos等待一段时间,直到途中计数器变成了0就返回,或者时间到了自动返回,或者等待时被中断
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
1.2.4 countDown()方法
public void countDown() {
sync.releaseShared(1);
}
需要准备线程调用。如果当前计数(也就是state)等于0,则什么也不做;
如果当前计数大于0,则尝试CAS将计数器递减1,递减成功如果新的计数为零,出于线程调度目的,将唤醒所有的因为调用await而等待的线程。
底层使用AQS的tryReleaseShared
方法
1.2.5 countDown()方法
public long getCount() {
return sync.getCount();
}
获取当前计数器的值,也就是AQS 的state 的值。
1.3 CountDownLatch的使用
具体看语雀另一篇CountDownLatch文章
1.4 CountDownLatch的总结
CountDownLatch利用AQS状态属性state来实现共享锁
在tryAcquireShared中只有state为0才表示"获取到锁",否则就会阻塞调用线程,在await方法使用;
在tryReleaseShared中只有state自减后为0才表示释放到锁,即只有当某个countDown方法将state变成0的时候,此时表示“成功释放了锁”,随后就会唤醒因为调用await方法而阻塞的线程,被唤醒的线程会判断到此时state=0,因此可以返回
countDown方法可以用在任何地方,这里的初始值N,可以是N个线程执行完毕之后调用N次countDown方法,也可以是1个线程里的N次调用countDown方法。
CountDownLatch一般用来确保某些活动直到其他活动都完成才继续执行,比如:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
- 等待直到某个操作所有参与者都准备就绪再继续执行。
CountDownLatch闭锁源码解析(基于jdk11)的更多相关文章
- String,StringBuffer和StringBuilder源码解析[基于JDK6]
最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...
- 【Java并发集合】ConcurrentHashMap源码解析基于JDK1.8
concurrentHashMap(基于jdk1.8) 类注释 所有的操作都是线程安全的,我们在使用时无需进行加锁. 多个线程同时进行put.remove等操作时并不会阻塞,可以同时进行,而HashT ...
- Spring源码解析-基于注解依赖注入
在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...
- Java 集合系列13之 WeakHashMap详细介绍(源码解析)和使用示例
概要 这一章,我们对WeakHashMap进行学习.我们先对WeakHashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用WeakHashMap.第1部分 WeakHashMap介绍 ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解Arra ...
- Java 集合系列06之 Vector详细介绍(源码解析)和使用示例
概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...
- Java 集合系列07之 Stack详细介绍(源码解析)和使用示例
概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...
- Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例
概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSe ...
- Java 集合系列17之 TreeSet详细介绍(源码解析)和使用示例
概要 这一章,我们对TreeSet进行学习.我们先对TreeSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeSet.内容包括:第1部分 TreeSet介绍第2部分 TreeSe ...
随机推荐
- 1.通俗易懂理解Kubernetes核心组件及原理
文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247483736&idx=1&sn=0cbc3d6a ...
- 安装ceph (快速) 步骤一:预检
官网地址:http://docs.ceph.org.cn/start/ 预检 安装一个 ceph-deploy 管理节点和一个三节点的Ceph 存储集群来研究 Ceph 的基本特性.这篇预检会帮你准备 ...
- Elasticsearch 开发入门 - Python
文章转载自:https://elasticstack.blog.csdn.net/article/details/111573923 前提条件 你需要在你的电脑上安装 python3 你需要安装 do ...
- NSIS V3.08 简体中文增强版
说明: 该3.08版本属本人业余时间集成修改制作,首发博客园,欢迎反馈安装与使用中出现的BUG,转载请注明出处! 本版本母版源自NSIS(Nullsoft Scriptable Install Sys ...
- vue项目使用.env文件配置全局环境变量
一.env文件的认识: (1).env 文件主要的作用是存储环境变量,也就是会随着环境变化的东西,比如数据库的用户名.密码.缓存驱动.时区,还有静态文件的存储路径之类的.因为这些信息应该是和环境绑定的 ...
- ZJOI2007报表统计
题目链接 比较简单的一道平衡树题. 第三个操作可以直接用map完成(加进去一个数只会让答案变小,于是与它的前面后面一个数做差更新答案即可),只考虑前两个操作. ·维护区间内的最大最小值,以及区间相邻两 ...
- 有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。
第一题 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数 ...
- `<jsp:getProperty>`动作和`<jsp:setProperty>`动作的使用在一个静态页面填写图书的基本信息,页面信息提交给其他页面,并且在其页面显示。要去将表单元素的值赋值给Java
<jsp:getProperty>动作和<jsp:setProperty>动作的使用 1.<jsp:getProperty>动作 语法格式: <jsp:get ...
- .NET周报【10月第3期 2022-10-25】
国内文章 聊一聊被 .NET程序员 遗忘的 COM 组件 https://www.cnblogs.com/huangxincheng/p/16799234.html 将Windows编程中经典的COM ...
- Mysql之MGR高可用实战案例
MGR高可用实战案例 1.环境准备 node1 rocky8.6 10.0.0.8 node2 rocky8.6 10.0.0.18 node3 rocky8.6 10.0.0.28 2.所有节点更改 ...