源码分析:CountDownLatch 之倒计时门栓
简介
CountDownLatch 是JDK1.5 开始提供的一种同步辅助工具,它允许一个或多个线程一直等待,直到其他线程执行的操作完成为止。在初始化的时候给定 CountDownLatch 一个计数,调用await() 方法的线程会一直等待,其他线程执行完操作后调用countDown(),当计数减到0 ,调用await() 方法的线程被唤醒继续执行。
应用场景
- 多线程并发下载或上传
主线程初始化一个为5的CountDownLatch ,然后分发给5个线程去完成下载或上传的动作,主线程等待其他线程完成任务后返回成功呢。 - 首页,一个复杂的查询包含多个子查询,但是子查询结果互相不依赖,也可以使用 CountDownLatch ,等待多个查询完成后再一起返回给首页。
源码分析
CountDownLatch 的源码相对于之前介绍的几个同步类,代码量要少很多很多,在JDK 1.8版本中也就300多行(包含注释),所以分析起来也比较简单。
内部类Sync
同样的,该内部类也继承了AQS,代码展示:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) { // 同步器的构造方法,初始化计数
setState(count);
}
...
}
主要的属性
主要的属性就一个,也就是内部类实例:同步器Sync
private final Sync sync;
构造方法
CountDownLatch 就一个构造方法,必须制定初始化计数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count); // 初始化同步器,指定计数
}
CountDownLatch 不算构造方法和toString方法一共也才4个方法,不多,所以我们全部看一下
await() 方法
调用该方法的线程会被阻塞,指定初始化的计数被减为0,或者线程被中断抛出异常。
代码展示:
// CountDownLatch.await()
public void await() throws InterruptedException { // 会抛出中断异常
sync.acquireSharedInterruptibly(1); //调用的是同步器框架AQS的方法
}
// AQS框架代码
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) // 检查线程中断状态,抛出异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 套路一样,调用Sync里面的方法
doAcquireSharedInterruptibly(arg); // 阻塞线程,排队,等待被唤醒
}
// 内部类Sync.tryAcquireShared()
protected int tryAcquireShared(int acquires) {
// 检查计数,如果为0,返回1,如果不为0,返回-1;
return (getState() == 0) ? 1 : -1;
}
await() 方法总结:
- 这应该是最简单的一个tryAcquireShared方法实现了。
- 仅调用了getState来检查当前计数,如果计数为0,返回1;如果计数不为0,返回-1。
- 阻塞线程,排队,等待被唤醒,中断抛出异常等逻辑都是在AQS实现的,具体分析请看之前的AQS分析文章
boolean await(timeout, unit)方法
和无参数的await()方法唯一的区别就是该方法指定了等待超时的时间,并且有返回值;
如果计数为0,则返回true;
如果线程被中断,则抛出异常;
如果线程经过了指定的等待时间,则返回false;
代码展示:
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted()) // 检查线程中断状态
throw new InterruptedException();
// tryAcquireShared 只会返回1或者-1,返回1代表计数已经为0,直接返回true
// doAcquireSharedNanos 是AQS 框架里面的代码
return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
}
// AQS 框架里面的代码
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 计算超时时间
final long deadline = System.nanoTime() + nanosTimeout;
// 构建当前排队节点,并加入队列,精灵王之前有分析
final Node node = addWaiter(Node.SHARED); //共享节点
boolean failed = true;
try {
for (;;) { // 自旋 tryAcquireShared(arg)
final Node p = node.predecessor();
if (p == head) { // 轮到当前节点了
int r = tryAcquireShared(arg);
if (r >= 0) { // 这里返回的大于等于0,说明计数为0,返回true
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false; // 超时了,直接返回false
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout); // 阻塞当前线程
if (Thread.interrupted()) // 中断抛出异常
throw new InterruptedException();
}
} finally {
if (failed) // 节点被取消
cancelAcquire(node);
}
}
countDown() 方法
如果当前计数大于零,则将其递减,如果计数达到零,则唤醒所有等待的线程(调用了await方法的线程)。如果当前计数等于零,那么什么也不会发生。源码展示:
public void countDown() {
sync.releaseShared(1); // 调用AQS递减计数
}
// AQS同步框架的代码
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 调用自己实现的方法tryReleaseShared
doReleaseShared(); //计数为0,唤醒所有等待的线程,返回true
return true;
}
return false;
}
// CDL 自己实现的递减计数方法
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) { // 自旋,保证递减操作成功
int c = getState(); // 当前的技术
if (c == 0) // 计数已经是0了,返回false,之后啥也不会发生
return false;
int nextc = c-1; // 递减
if (compareAndSetState(c, nextc)) // cas 更新计数
return nextc == 0; 计数为0才返回true
}
}
// 唤醒等待的线程
private void doReleaseShared() {
for (;;) { //自旋操作
Node h = head;
if (h != null && h != tail) { // 等待的线程队列不为空
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {// 检查状态是否要唤醒下一个节点的线程
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // CAS 失败了才会继续continue
continue; // loop to recheck cases
unparkSuccessor(h); // 唤醒头节点的下一个节点线程
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 头节点没变
if (h == head) // loop if head changed
break;
}
}
countDown() 方法总结:
- 主要逻辑就是把计数减1
- 如果计数减到了0,则唤醒所有队列中等待的线程
- 如果减之前计数已经是0了,则什么也不干
getCount() 方法
public long getCount() { // CDL 的API
return sync.getCount();
}
// 内部类 Sync
int getCount() {
return getState();
}
// AQS 框架api
protected final int getState() {
return state;
}
返回当前的计数。
CountDownLatch 总结
- 主要功能维护计数,当计数减为零后才放开所有等待的线程
- CountDownLatch 没有加计数的API,所以一个CountDownLatch不可以重复使用,如果要用可以重置计数的,可以使用CyclicBarrier。
- CountDownLatch 也会有“死锁”的现象,要避免计数永远减不到0的情况
- 如果初始化计数为0,那么 CountDownLatch 则毫无作用,不如不用
- 如果初始化计数为1,调用await时阻塞自己,别人countDown解锁后,再唤醒自己(类似于在等一个资源,拿到资源在继续进行)
和Semaphore的区别
Semaphore 可以用来限流,比如限制一个景区最多允许10000人同时在园内,只有当有人出园后,才允许其他人入园。
CountDownLatch 可以用来计数,比如导游在出发点等待10名游客一起出发,来一名游客就画个叉,直到10名游客到齐后,才一起出发去旅游。
源码分析:CountDownLatch 之倒计时门栓的更多相关文章
- 【JDK】JDK源码分析-CountDownLatch
概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...
- 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析
这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...
- 【JUC】JDK1.8源码分析之CountDownLatch(五)
一.前言 分析完了CyclicBarrier后,下面分析CountDownLatch,CountDownLatch用于同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成.CountDownL ...
- Java - "JUC" CountDownLatch源码分析
Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...
- 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理
1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...
- CountDownLatch 源码分析
CountDownLatch 源码分析: 1:CountDownLatch数据结构 成员变量 Sync类型对象 private final Sync sync; Sync是继承AQS的一个类,Coun ...
- concurrent(五)同步辅助器CountDownLatch & 源码分析
参考文档: https://blog.csdn.net/zxdfc/article/details/52752803 简介 CountDownLatch是一个同步辅助类.允许一个或多个线程等待其他线程 ...
- Android 关于 CountDownTimer onTick() 倒计时不准确问题源码分析
一.问题 CountDownTimer 使用比较简单,设置 5 秒的倒计时,间隔为 1 秒. final String TAG = "CountDownTimer"; * , ) ...
- 并发工具CountDownLatch源码分析
CountDownLatch的作用类似于Thread.join()方法,但比join()更加灵活.它可以等待多个线程(取决于实例化时声明的数量)都达到预期状态或者完成工作以后,通知其他正在等待的线程继 ...
随机推荐
- centos8下启用rc-local服务
一,centos8不建议写rc.local,默认启动时执行的命令放到何处? 以前我们会把linux开机执行的命令写入到/etc/rc.local 在centos8上系统不再建议我们写入到rc.loca ...
- Ubuntu搜狗输入法安装
一.下载sogoupinyin_2.2.0.0108_amd64.deb 二.拷贝到服务器并安装 sudo dpkg -i sogoupinyin_2.2.0.0108_amd64.deb 三.设置搜 ...
- S10 TES的八强赛,创造了奇迹,看看比赛时网友们怎么说的
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 我相信很多观众会和我一样吧,当TES赢下第一局后开始心怀侥幸,赢下第二局后觉 ...
- postgresql 导出数据库与数据表
单表导出 pg_dump --host 127.0.0.1 --port 5432 --username "postgres" --role "postgres" ...
- Jquery中$("").事件()和$("").on("事件","指定的元素",function(){});的区别(jQuery动态绑定事件)
这个是在学习时不懂的问题,记录下来方便查看 转至https://www.cnblogs.com/mr-wuxiansheng/p/7136864.html //绑定 下一页 的点击事件 $(" ...
- 没事学学KVM(二)创建一台虚拟机
首先通过VMware创建一台虚机,建议内存大于1G,并开启CPU 的inter vt-x功能,安装好对应的软件后,yum install -y qemu-kvm* virt-* libvirt* 准备 ...
- python fastApi实战项目 - 爱投票管理系统(一)
一.闲来无事,在工作之余自己研究了一下python的异步框架 - fastapi,并写包括 1.部门管理 2.角色管理 3.用户管理 4.菜单管理 5.登录日志 6.操作日志 六个基础功能模块,演示链 ...
- 《SQL 必知必会》读书笔记
第1课 了解 SQL 这章主要介绍了数据库,表,字段类型,行,列,主键和SQL等基本概念. 数据库:以某种形式存储的数据集合,在计算机上的表现形式可能是一个文件或者一组文件.我们平时所说的数据库,往往 ...
- C--迷途指针
简介: 在计算机编程领域中,迷途指针,或称悬空指针.野指针,指的是不指向任何合法的对象的指针. 当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情 ...
- 【总结】mybatis
一.config配置文件详解 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE config ...