透过CountDownLatch窥探AQS
本文来自公众号“Kahuna”,可搜索Alitaba119,欢迎关注,转载请注明出处,非常感谢
“ 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类用法
三、CountDownLatch类源码解析
四、CountDownLatch类的思考点
五、CountDownLatch类的适用场景
六、文章透过ReentrantLock窥探AQS问题回答
七、AQS系列推荐
一、CountDownLatch类用法
先看下我们的测试代码,waitThread1和waitThread2两个线程等待latch,另外5个任务线程执行到latch.countDown(),任务就绪(此时任务线程会继续执行后面的逻辑),等最后一个任务执行latch.countDown()之后waitThread1,waitThread2被唤醒继续执行。
下图是两次执行结果:
从上图结果中可以看出来,waitThread1和waitThread2两个线程等待5个任务线程就绪之后才继续执行;另外waitThread1和waitThread2的执行和5个任务线程的执行没有固定的先后顺序,任务线程执行完countDown()方法之后会继续执行后面的逻辑。
二、CountDownLatch类工作流程
通过下面这个动图来展示CountDownLatch的整个工作流程,需要注意的是等待线程和任务线程的区别,另外在任务就绪之后,整个释放的流程需要重点关注,共享节点的释放并不是固定一个个节点按顺序释放的,而是可能多个节点同时释放的。
三、CountDownLatch类源码解析
CountDownLatch源码相对来说还是比较简单的,最核心的就是理解他的工作步骤以及同步器的实现。
工作步骤:
- 初始化的时候定义几个任务,即同步器中state的数量
- 等待线程执行await方法等待state变成0,等待线程会进入同步器的等待队列
- 任务线程执行countDown方法之后,state值减1,直到减到0,唤醒等待队列中所有的等待线程。
同步器实现了tryAcquireShared方法,判断当state!=0的时候把等待线程加入到等待队列并阻塞等待线程,state=0的时候这个latch就不能够再向等待队列添加等待线程;另外实现了tryReleaseShared,判断当前任务是否是最后一个任务,当state减到0的时候就是最后一个任务,然后会以传播唤醒的方式唤醒等待队列中的所有等待线程。
四、CountDownLatch类的思考点
这边的思考点只有一个,latch任务线程都就绪之后怎么释放?要了解这个点,我们需要具体看下AQS中的doReleaseShared方法。
不知道大家有没有想过,上图1,2,3处代码,作者为什么要这么写?
在解释这三处代码之前,我们首先要弄清楚SHARD节点的释放方式。
假如我们有以下的共享节点组成的AQS等待队列:
主线程调用doReleaseShared方法,进入for循环自旋,head节点的地址赋值给h,然后判断是否有后继节点并需要释放,最后判断h中的地址和当前的head节点的地址是否一样,如果一样,则退出for循环,流程图如下:
每一个被唤醒的线程都会进入自旋逻辑,而且每个线程都有一个head地址的本地变量副本,当这个副本和当前的head节点的地址不一致的时候,会继续自旋, 获取最新的head节点,去尝试唤醒下一个线程。我们看下下面的这几种时序:
第一处代码:为什么需要把waitStatus设置成0?
如果waitStatus=NODE.SIGNAL,不把状态设置成0,则自旋的时候有可能重复触发唤醒同一个节点(上图中如果主线程,t1线程,t2线程自旋的时候,head指向线程t3节点,waitStatus如果等于-1,则线程t4就会被重复唤醒)。
第二处代码:为什么等于0的时候要设置NODE.PROPAGATE?
还是拿上面的case举例子,假如现在head指向线程t4,如果主线程,t1线程,t2线程,t3线程同时完成自己的唤醒任务,然后都获取了线程t4的head地址,第一个线程把线程t4节点的waitStatus改成了0,那后面的线程会尝试把0改成PROPAGATE(这里过来的线程有两种可能,修改waitStatus从-1到0失败继续自旋过来,或者是判断waitStatus=0过来),如果不成功,则自旋,如果成功则判断head是否有变化,简单的说就是让竞争修改waitStatus为PROPAGATE失败的线程继续释放其他的节点。
第三处代码:为什么要设置h==head退出自旋?
最主要的原因是,不然我怎么跳出自旋呢!另外如果head不一致,当前线程可以帮忙一起释放剩余的等待线程,提高释放效率。
五、CountDownLatch类的适用场景
如果你要执行一个任务,但是必须先等其他几个任务做到某个程度这个任务才能够启动,这个时候就比较适合用CountDownLatch。但是一般会使用超时等待的方式来处理,不然如果其中某个任务异常没有完成,或者超时了,那么任务会一直等待在那里。
六、文章透过ReentrantLock窥探AQS问题回答
目前ReentrantLock窥探AQS文章还没收集到问题,有兴趣的同学可以点进去看看,在文章下留言就行
七、AQS系列推荐
“ 本系列文章旨在对并发包中的AQS以及跟AQS相关的锁做一个深入的讲解,让各位看官可以深入理解AQS以及对应的锁”
AQS条件队列和同步队列的关系
透过ReentrantReadWriteLock窥探AQS
通过Semaphore窥探AQS
尾声
tips:文章中的部分图片是传到微博上再导过来的(微信文章中的不能直接用),有点糊,如果想要高清的图片可以关注我的公众号Alitaba119,公众号文章中的图片比较清楚。
如果本文章解决了各位看官心里的一些疑惑,可以关注本公众号,后续会有更多精彩文章,也可以帮忙推荐给其他的小伙伴,让更多的人受益,万分感谢。
如果看了文章之后心中还有疑惑,请在文章下面留言,下一篇文章「AQS条件队列和同步队列的关系」中我会对前一篇文章大家的问题做一个解答。
透过CountDownLatch窥探AQS的更多相关文章
- 透过ReentrantLock窥探AQS
背景 JDK1.5引入的并发包提供了一系列支持中等并发的类,这些组件是一系列的同步器,几乎任一同步器都可以实现其他形式的同步器,例如,可以用可重入锁实现信号量或者用信号量实现可重入锁.但是,这样做带来 ...
- Java多线程之---用 CountDownLatch 说明 AQS 的实现原理
本文基于 jdk 1.8 . CountDownLatch 的使用 前面的文章中说到了 volatile 以及用 volatile 来实现自旋锁,例如 java.util.concurrent.ato ...
- 透过 ReentrantLock 分析 AQS 的实现原理
对于 Java 开发者来说,都会碰到多线程访问公共资源的情况,这时候,往往都是通过加锁来保证访问资源结果的正确性.在 java 中通常采用下面两种方式来解决加锁得问题: synchronized 关键 ...
- Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例
概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...
- JUC系列回顾之-CountDownLatch底层原理和示例
CountDownLatch 是一个同步工具类,允许一个线程或者多个线程等待其他线程完成操作,再执行. CountDownLatch(int count) 构造一个用给定计数初始化的 CountDow ...
- 解读java同步类CountDownLatch
同步辅助类: CountDownLatch是一个同步辅助类,在jdk5中引入,它允许一个或多个线程等待其他线程操作完成之后才执行. 实现原理 : CountDownLatch是通过计数器的方式来实现, ...
- 并发是个什么鬼之同步工具类CountDownLatch
扯淡 写这篇文章,我先酝酿一下,实不相瞒,脱离底层太久了,更确切的情况是,真没曾认真研究过.就目前来说,很多框架包括工具类已经把实现封装的很深,你只需轻轻的调用一下API,便不费半点力气.以至于大家会 ...
- Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析
1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...
- 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析
这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...
随机推荐
- context.xml文件配置
<?xml version='1.0' encoding='utf-8'?> <Context> <WatchedResource>WEB-INF/web.xml& ...
- Java注解-注解处理器、servlet3.0|乐字节
大家好,我是乐字节的小乐,上次给大家带来了Java注解-元数据.注解分类.内置注解和自定义注解|乐字节,这次接着往下讲注解处理器和servlet3.0 一.注解处理器 使用注解的过程中,很重要的一部分 ...
- C++编译提示 default argument are given of parameter ..
如果定义一个类的构造函数时,带有默认的入参值,在cpp文件中实现构造函数时,是不能带的!否则就会提示该种类型的编译错误. //.h文件: namespace Ui { class Task; } cl ...
- Linux02 cd命令以及绝对路径和相对路径
一.cd 这是一个非常基本,也是大家常用的命令,用于切换当前目录,他的参数就是要切换的目录的路径,可以是绝对路径,也可以是相对路径. cd /home/keshengtao/ 绝对路径 cd ./pa ...
- Redis--set类型操作命令
集合类型 set redis 的 Set 是 string 类型的无序集合,集合成员是唯一的,即集合中不能出现重复的数据 集合类型 set ——常用命令 sadd /smembers /sismemb ...
- Python之推导式笔记
观察下面的代码: list1 = [] for i in range(10): list1.append(i) print(list1) 作为一个Java出身的程序员,我一定会这么写代码去生成一个列表 ...
- #448 div2 a Pizza Separation
A. Pizza Separation time limit per test1 second memory limit per test256 megabytes inputstandard inp ...
- MyBatis 常用词汇含义
JDBC:java Data Base Connection(Java与数据库连接): ORM:Object Relational Mapping(对象关系映射,简称ORM,或者O/RM,或者O/M ...
- beego 框架基本使用 && 知识点整理
beego 官网的教程已经整理的非常详细了,但作为一个刚接触的学习者,还是有必要做一下整理,这样在后面使用的时候遇到了不太熟悉的地方,还能反过头来看下自己整理的内容,快速的把知识再捞回来,下面是对官网 ...
- [转发]C++中new和malloc的区别
原文地址:https://blog.csdn.net/linux_ever/article/details/50533149 new与malloc的10点区别 1. 申请的内存所在位置 new操作符从 ...