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 作品涌现出来. ...
随机推荐
- Spring与Shiro整合
Spring整合篇--Shiro 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 什么是Shiro? 链接:https://www.cnblogs.com/StanleyBlogs/ ...
- Elasticsearch系列---全面了解Document
概要 本篇主要介绍一下document的知识,对document的元数据和基本的语法进行讲解. document核心元数据 前面入门实战一节有简单介绍过document数据示例,这次我们来详细了解一下 ...
- 2019-9-9:渗透测试,docker下载dvwa,使用报错型sql注入dvwa
docker下载dvwa镜像,报错型注入dvwa,low级 一,安装并配置docker 1,更新源,apt-get update && apt-get upgrade &&am ...
- Mysql查询语句之排序查询
语法: /* select 查询列表 from 表 [where 筛选条件] order by 排序列表 [asc/desc] */ ①asc为升序,desc为降序,且默认为升序 ②order by子 ...
- Windows Server 2008 服务器重启后卡死在Windows Update 页面问题处理
Windows Update 服务器 服务器是联想RD640 操作系统Windows Server 2008 R2 Enterprise版 补丁版本是SP1 远程windows服务器时,一直处于远程建 ...
- Redis的面试问题总结,面试跳槽必备
1.什么是redis? Redis 是一个基于内存的高性能key-value数据库. 2.Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库 ...
- 从Excel到Python:最常用的36个Pandas函数
本文涉及pandas最常用的36个函数,通过这些函数介绍如何完成数据生成和导入.数据清洗.预处理,以及最常见的数据分类,数据筛选,分类汇总,透视等最常见的操作. 生成数据表 常见的生成数据表的方法有两 ...
- 从零开始入门 K8s | 深入剖析 Linux 容器
作者 | 唐华敏(华敏) 阿里云容器平台技术专家 本文整理自<CNCF x Alibaba 云原生技术公开课>第 15 讲. 关注"阿里巴巴云原生"公众号,回复关键词 ...
- diff()函数的使用
1.diff():返回略微迭代(lagged)的或滞后的不同(iterated diferences). > x<-cumsum(cumsum(1:10)) > x [1] 1 4 ...
- Spring Data JPA 条件查询的关键字
Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下: And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(Stri ...