AQS系列(五)- CountDownLatch的使用及原理
前言
前面四节学完了AQS最难的两种重入锁应用,下面两节进入实战学习,看看JUC包中其他的工具类是如何运用AQS实现特定功能的。今天一起看一下CountDownLatch。
CountDownLatch可以用来实现多个线程执行完一个功能后让另一个线程继续执行的功能。常见的场景比如大文件的处理,我们需要对一个或多个文件进行处理,处理完之后再统一入库,这时我们就可以用到CountDownLatch了。
一、使用样例
public static void main(String[] args) {
// 指定初始容量
CountDownLatch latch = new CountDownLatch(3);
// 启动三个线程,每个线程独自处理文件
for (int i = 0;i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 正在处理文件");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 处理完毕");
latch.countDown();
}).start();
}
try {
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有文件处理完成后,统一入库");
}
运行结果:
Thread-0 正在处理文件
Thread-2 正在处理文件
Thread-1 正在处理文件
Thread-0 处理完毕
Thread-2 处理完毕
Thread-1 处理完毕
所有文件处理完成后,统一入库
效果就是这样,下面我们一起看看它是如何实现的这种功能。
二、源码学习
1、首先我们看看new CountDownLatch(3) 做了什么事情
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
继续追踪可以发现,就是将count赋值给AQS中的成员变量state,表示已经有3个线程占用了锁。
2、看countDown()方法做了什么
public void countDown() {
sync.releaseShared(1);
}
可以看到,countDown走的是释放共享锁的逻辑,从给state赋值也可以猜到用的是共享锁-有多个线程且state可赋大于0的值。继续看releaseShared逻辑:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
可以看到就是读锁释放的逻辑,其中doReleaseShared方法实现逻辑相同就不看了,不同的是tryReleaseShared方法,下面跟进:
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
此方法在CountDownLatch中的内部类Sync中得到实现,逻辑为将state-1,并且如果是0的话返回true。返回true后在releaseShared方法中会进入if里面,走唤醒后续节点的逻辑doReleaseShared方法,在该方法中唤醒的main线程。main线程什么时候被挂起的?且看下面。
3、await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
await调用了可响应中断的获取共享锁方法,继续查看:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
此方法是AQS中的公用模板方法,不同点在于各实现类的实现逻辑,在CountDownLatch中对tryAcquireShared方法进行了实现,实现逻辑如下:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
即如果state==0则能获取到锁,否则获取不到。获取不到进入下面的doAcquireSharedInterruptibly方法,最终会将head的waitStatus设置为-1,自己挂起等待唤醒。
三、总结
CountDownLatch是基于共享锁实现的并发控制功能,现在对总的实现逻辑做个梳理:首先在构造器初始化CountDownLatch的时候,就会给AQS中的state赋值,表示共享锁已经被获取了N次;然后每执行一次countDown则共享锁释放一次,直到释放完;await方法是加锁的逻辑,但加锁条件是state==0时才会加锁成功,否则挂起;最后,当通过countDown的调用将state减为0后,会唤醒处于阻塞状态的主线程,让其 获取到锁并执行。
AQS系列(五)- CountDownLatch的使用及原理的更多相关文章
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- java多线程系列(八)---CountDownLatch和CyclicBarrie
CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...
- AQS系列(七)- 终篇:AQS总结
前言 本文是对之前AQS系列文章的一个小结,首先看看以下几个问题: 1.ReentrantLock和ReentrantReadWriteLock的可重入特性是如何实现的? 2.哪个变量控制着锁是否被占 ...
- java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...
- AQS系列(一)- ReentrantLock的加锁
前言 AQS即AbstractQueuedSynchronizer,是JUC包中的一个核心抽象类,JUC包中的绝大多数功能都是直接或间接通过它来实现的.本文是AQS系列的第一篇,后面会持续更新多篇,争 ...
- AQS系列(三)- ReentrantReadWriteLock读写锁的加锁
前言 前两篇我们讲述了ReentrantLock的加锁释放锁过程,相对而言比较简单,本篇进入深水区,看看ReentrantReadWriteLock-读写锁的加锁过程是如何实现的,继续拜读老Lea凌厉 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
- 深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...
- CSS 魔法系列:纯 CSS 绘制各种图形《系列五》
我们的网页因为 CSS 而呈现千变万化的风格.这一看似简单的样式语言在使用中非常灵活,只要你发挥创意就能实现很多比人想象不到的效果.特别是随着 CSS3 的广泛使用,更多新奇的 CSS 作品涌现出来. ...
随机推荐
- Android View 的添加绘制流程 (二)
概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最 ...
- java中的运算,+-* /% | ^ &
java中运算都是操作符号,那么整形默认为int,双精度默认为都double 整数 看案例: 无法编译通过:操作默认为int,接受结果为int,所以这个地方编译无法通过,所以需要强制类型转换 再看案例 ...
- 利用scrapy爬取腾讯的招聘信息
利用scrapy框架抓取腾讯的招聘信息,爬取地址为:https://hr.tencent.com/position.php 抓取字段包括:招聘岗位,人数,工作地点,发布时间,及具体的工作要求和工作任务 ...
- yum运行报错:File "/usr/libexec/urlgrabber-ext-down", line 28
[root@sdw1 bin]# vim /usr/libexec/urlgrabber-ext-down 再次执行yum命令,正常下载
- Virtualbox修改虚拟机分配内存的大小
起因:因为虚拟机刚开始分配的内存太小,导致太卡, 解决方法:修改虚拟机分配内存的大小 方法一:必须在关闭ubuntu的前提下进行,否则无法修改 点击设置 系统选项 主板中的内存大小 之后开启即可 方法 ...
- MySQL8——源码安装
一.环境 ubuntu18.10(CentOS7由于gcc版本过低已经无法通过编译) 二.准备工作 1.安装依赖 apt-get install -y gcc g++ cmake build-esse ...
- 2753:走迷宫(dfs+初剪)//可以说是很水了。。。
总时间限制: 1000ms 内存限制: 65536kB 描述 一个迷宫由R行C列格子组成,有的格子里有障碍物,不能走:有的格子是空地,可以走.给定一个迷宫,求从左上角走到右下角最少需要走多少步(数 ...
- MySQL分层和查询数据的流程
MySQL分层 MySQL分层 主要分为:连接层,服务层,引擎层,存储层 客户端执行一条select命令的流程如下 连接器 功能: 负责跟客户端建立连接.获取权限.维持和管理连接 细节: 1.当用户登 ...
- 伪共享 FalseSharing (CacheLine,MESI) 浅析以及Java里的解决方案
起因 在阅读百度的发号器 uid-generator 源码的过程中,发现了一段很奇怪的代码: /** * Represents a padded {@link AtomicLong} to preve ...
- ThinkPHP5——安装验证码和使用
1.使用composer安装验证码 首先要安装composer,大部分“composer require topthink/think-captcha”命令无法运行或者提示不是内部文件或可执行命令,都 ...