Java读源码之CountDownLatch
前言
相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行。
案例
用一个最简单的案例引出我们的主角
public class CountDownLatchDemo {
public void run(CountDownLatch countDownLatch) {
System.out.println(Thread.currentThread().getName() + "就位");
countDownLatch.countDown();
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
CountDownLatch countDownLatch = new CountDownLatch(5);
IntStream.rangeClosed(0, 4)
.forEach(num -> executorService
.execute(() -> countDownLatchDemo.run(countDownLatch))
);
countDownLatch.await();
System.out.println("已到齐");
}
/**
* 输出:
* pool-1-thread-2就位
* pool-1-thread-5就位
* pool-1-thread-4就位
* pool-1-thread-3就位
* pool-1-thread-1就位
* 已到齐
*/
}
源码分析
看源码前最好先熟悉下 AQS 的大致结构,之前有两篇文章仅供参考,大致熟悉下即可
在看 AQS 的 Node 节点的时候看到有共享模式和独占模式,ReentrantLock 用了独占模式,CountDownLatch 正式用了共享模式,相信看完能够对 AQS 有更深的理解。
初始化
- CountDownLatch#CountDownLatch
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
// 可以看到 CountDownLatc 内部也实现了一个 AQS
this.sync = new Sync(count);
}
- CountDownLatch.Sync#Sync
Sync(int count) {
// 直接拿了 count 把锁(当前线程可重入 Sync 锁 count 次)
setState(count);
}
等待
- CountDownLatch#await
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 注意,持有锁的线程被中断是直接抛异常的
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared很简单,如果全员到齐了返回1,其他时候都返回 -1
if (tryAcquireShared(arg) < 0)
// 所以没到齐前都会以共享模式入同步队列
doAcquireSharedInterruptibly(arg);
}
- AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// addWaiter之前看过,作用就是把节点放到同步队列的末尾,但是这里节点类型是共享模式
// 值得注意的是,模式是存在节点的 nextWaiter 中,所以不管 nextWaiter 可能三种情况 1。独占模式的空节点 2.共享模式的空节点 3。Condition条件队列的下一个节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
// 下面的自旋和 acquireQueued 方法基本一模一样,重点看下区别
try {
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是 head 说明没人排队
if (p == head) {
// 再次尝试
int r = tryAcquireShared(arg);
// 只有调用了足够次数countDown,栅栏才会打开
if (r >= 0) {
// 这里的 r 一定为 1,会一个一个唤醒所有节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 一般入队肯定有人排队的,之前也看过,主要作用,通知前驱节点,然后挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- AbstractQueuedSynchronizer#setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate = 1 次判断一定为 true
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// CountDownLatch节点所有节点都是共享模式,一定满足
if (s == null || s.isShared())
// 直接唤醒下一个
doReleaseShared();
}
}
- AbstractQueuedSynchronizer#doReleaseShared
private void doReleaseShared() {
for (;;) {
Node h = head;
// 同步队列不为空则进入
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果有节点需要被唤醒
if (ws == Node.SIGNAL) {
// 不断重试 CAS 吧 头节点设为全新
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒下一个节点
unparkSuccessor(h);
}
// 到这里 下一个已经唤醒了,把节点状态设置为 PROPAGATE,说明是共享状态唤醒的
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 直到头节点变化结束,也就是下一个一个被唤醒了,然后再由下一个接着唤醒
if (h == head)
break;
}
}
签到
看等待过程,栅栏打开后,所有共享模式的节点会一个一个的唤醒,让我们一起看看如何打开栅栏并唤醒第一个节点。
- AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 这个方法和等待时候自悬的一样,用于唤醒第一个节点
doReleaseShared();
return true;
}
return false;
}
- CountDownLatch#tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
// c == 0 说明栅栏已经打开过了,CountDownLatch 是一次性的,直接false
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
// cas 递减状态,达到0的时候返回 true 栅栏打开
return nextc == 0;
}
}
Java读源码之CountDownLatch的更多相关文章
- Java读源码之ReentrantLock
前言 ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能.但 R ...
- Java读源码之ReentrantLock(2)
前言 本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑. Java读源 ...
- Java读源码之Thread
前言 JDK版本:1.8 阅读了Object的源码,wait和notify方法与线程联系紧密,而且多线程已经是必备知识,那保持习惯,就从多线程的源头Thread类开始读起吧.由于该类比较长,只读重要部 ...
- Java读源码之ThreadLocal
前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...
- Java读源码之Object
前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...
- Java读源码之LockSupport
前言 JDK版本: 1.8 作用 LockSupport类主要提供了park和unpark两个native方法,用于阻塞和唤醒线程.注释中有这么一段: 这个类是为拥有更高级别抽象的并发类服务的,开发中 ...
- java读源码 之 map源码分析(HashMap,图解)一
开篇之前,先说几句题外话,写博客也一年多了,一直没找到一种好的输出方式,博客质量其实也不高,很多时候都是赶着写出来的,最近也思考了很多,以后的博客也会更注重质量,同时也尽量写的不那么生硬,能让大家 ...
- java读源码 之 queue源码分析(PriorityQueue,附图)
今天要介绍的是基础容器类(为了与并发容器类区分开来而命名的名字)中的另一个成员--PriorityQueue,它的大名叫做优先级队列,想必即使没有用过也该有所耳闻吧,什么?没..没听过?emmm... ...
- java读源码 之 list源码分析(LinkedList)
文章目录 LinkedList: 继承关系分析: 字段分析: 构造函数分析: 方法分析: LinkedList: 继承关系分析: public class LinkedList<E> ex ...
随机推荐
- MySQL对JSON类型UTF-8编码导致中文乱码探讨
前言 继上文发表之后,结合评论意见并亲自验证最终发现是编码的问题,但是对于字符编码还是有点不解,于是乎,有了本文,我们来学习字符编码,在学习的过程中,我发现对于MySQL中JSON类型的编码导致数据中 ...
- Django HttpResponse
HttpResponse 概述:给浏览器返回数据 HttpRequest对象是由django创建的,HttpResponse对象由程序员创建 用法 1:不调用模板,直接返回数据. 例: def get ...
- Centos 8 安装 Consul-Template
1. 下载安装包( consul-template_0.23.0_linux_amd64.zip 文件 ) 下载地址: https://releases.hashicorp.com/consul-te ...
- 自签SSL证书
0.介绍 自己开发的使用了SSL协议的软件,通常没必要从证书签发机构那里来获取证书,自签证书成了必要的选择.自签证书还可以用来实现客户端登录认证. 1.创建CA 创建CA的私钥 openssl gen ...
- js的预编译
JavaScript不会完全按照代码的顺序执行,在执行之前会对定义的函数和变量先来一边所谓的预编译处理. 先来说下对变量的预处理: console.log(a) //undefined var a = ...
- Docker搭建MySQL主从复制
Docker搭建MySQL主从复制 主从服务器上分别安装Docker 1.1 Docker 要求 CentOS 系统的内核版本高于 3.10 [root@localhost ~]# uname -r ...
- 【每周小项目】使用 puppeteer 插件爬取动态网站
目录 0. 前言 问题 解决 1. 下载与引包 2. 使用步骤 3. 爬过的几个坑 page.evaluate 的传参问题 元素操作问题 0. 前言 这两天对爬虫开始感兴趣,最开始是源于天涯的一个房价 ...
- NKOJ3775 数列操作
问题描述 给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0.请找到最长的一段连续区间,使得该区间内所有数字之和不超过p. 输入格式 第一行包含三个整数n ...
- Light of future-冲刺Day 3
目录 1.SCRUM部分: 每个成员进度 SCRUM 会议的照片 签入记录 代码运行截图 发布界面 用户浏览界面 管理员浏览界面 2.PM 报告: 时间表 燃尽图 任务总量变化曲线 每名成员的贡献比 ...
- SpringMVC(三):转发和重定型
本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...