CountDownLatch闭锁源码解析(基于jdk11)

1.1 CountDownLatch概述

public class CountDownLatch extends Object

CountDownLatch是一种同步工具,常被称为"闭锁",也叫做"倒计数器"。在完成一组正在其他线程中执行的操作之前,CountDownLatch允许一个或多个线程一直等待。

很明显,这类似于在开始某个行为之前的准备操作

比如有一个任务A,它要等待其他4个任务完成后才能执行后续工作,此时就可以利用CountDownLatch。

1.2 CountDownLatch原理

1.2.1 基本结构(jdk11)

UML类图可知,CountDownLatch内部同样也使用了AQS实现功能,大胆猜测与AQS里的state状态属性有关。

CountDownLatch的构造函数接受了一个int类型的count参数作为计数器,如果你想等待N个线程计数,那就传入N。通过构造函数,实际上是把count赋值给了AQS里的同步状态属性state。

1、

2、

3、

在CountDownLatch的Sync实现中,重写了tryAcquireShared和tryReleaseShared方法,此处可看出是一个共享锁。

一般情况下(常见Lock锁的实现中)我们在释放锁的时候会将state资源减少,获得锁的时候会将state资源增加,当state变为0表示释放锁成功或者没有线程获取到锁,但是CountDownLatch中state的含义则不一样:

  1. 在尝试获取锁的tryAcquireShared中,虽然名字叫获取锁,但事里面逻辑却只做了一个判断,如果state为0就表示获得了锁,state为其他值的情况下都没有获取锁,tryAcquireShared方法在awit()系列方法中被调用。
  2. 在尝试释放锁的tryReleaseShared方法中,虽然名字叫释放锁,但却仅仅是在对state尝试自减操作,它的内部是一个循环操作,每一次的调用tryReleaseShared都会首先判断state是否为0,如果是,那么返回false表示“释放锁失败”,如果不是那么尝试CAS的将state自减1,CAS成功之后会判断此时的值是否为0,如果不是那么表示“释放锁失败”,返回false,否则表示“释放锁成功”,返回true,这里的操作可以永远保证只有一个线程能够因为“释放锁成功”而返回true。tryReleaseShared方法在countDown()方法中被调用。

1.2.2 await()方法

 public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

需要等待的线程调用。调用该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:

  1. 当计数器的值为0 时;
  2. 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException 异常,然后返回。

根据源码,想要调用await方法的线程能够返回,一般情况下需要获取到共享锁,而CountDownLatch内部的tryAcquireShared返回大于0的要求是state为0,即只有在state为0的时候,调用await方法的线程才能能够返回。

/**
* CountDownLatch 的await方法
*
* @throws InterruptedException 等待时被中断
*/
public void await() throws InterruptedException {
//调用了AQS 的acquireSharedInterruptibly方法,共享式可中断获取锁
sync.acquireSharedInterruptibly(1);
} /**
* AQS 的acquireSharedInterruptibly方法
* 共享式获取同步状态,可以被中断,在AQS部分我们已经讲过了
*
* @param arg 参数,在实现的时候可以传递自己想要的数据,这里没什么用
* @throws InterruptedException 等待时被中断
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquireShared方法由AQS的子类实现,尝试共享式获取锁,如果返回值小于0,表示获取失败
if (tryAcquireShared(arg) < 0)
//获取锁失败的线程进入AQS的队列等待,在被唤醒之后还是会继续调用tryAcquireShared获取锁,直到获得锁成功
doAcquireSharedInterruptibly(arg);
}

1.2.3 await(timeout, unit)方法

public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

需要等待的线程调用。调用该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:

  1. 当计数器值为0 时,这时候会返回true ;
  2. 设置的timeout 时间到了,因为超时而返回false ;
  3. 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException 异常,然后返回。

与空参await()方法类似,内部使用了AQS的tryAcquireSharedNanos,而空参await()则是使用acquireSharedInterruptibly

/**
* CountDownLatch 的await( timeout, unit)方法
* 超时等待
*
* @param timeout 等待时间
* @param unit 时间单位
* @return true 成功 false 失败
* @throws InterruptedException 被中断
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
//调用了AQS 的tryAcquireSharedNanos方法,共享式超时可中断获取锁
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} /**
* AQS 的tryAcquireSharedNanos方法
* 共享式超时获取锁,可以被中断,在AQS部分我们已经讲过了
*
* @param arg 参数
* @param nanosTimeout 超时时间,纳秒
* @return 是否获取锁成功
* @throws InterruptedException 被中断
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//最开始就检查一次,如果当前线程是被中断状态,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//下面是一个||运算进行短路连接的代码
//tryAcquireShared尝试获取锁,获取到了(返回大于等于0)直接返回true
//获取不到(左边表达式为false) 就执行doAcquireSharedNanos方法
//doAcquireSharedNanos等待一段时间,直到途中计数器变成了0就返回,或者时间到了自动返回,或者等待时被中断
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}

1.2.4 countDown()方法

public void countDown() {
sync.releaseShared(1);
}

需要准备线程调用。如果当前计数(也就是state)等于0,则什么也不做;

如果当前计数大于0,则尝试CAS将计数器递减1,递减成功如果新的计数为零,出于线程调度目的,将唤醒所有的因为调用await而等待的线程。

底层使用AQS的tryReleaseShared方法

1.2.5 countDown()方法

public long getCount() {
return sync.getCount();
}

获取当前计数器的值,也就是AQS 的state 的值。

1.3 CountDownLatch的使用

具体看语雀另一篇CountDownLatch文章

1.4 CountDownLatch的总结

CountDownLatch利用AQS状态属性state来实现共享锁

在tryAcquireShared中只有state为0才表示"获取到锁",否则就会阻塞调用线程,在await方法使用;

在tryReleaseShared中只有state自减后为0才表示释放到锁,即只有当某个countDown方法将state变成0的时候,此时表示“成功释放了锁”,随后就会唤醒因为调用await方法而阻塞的线程,被唤醒的线程会判断到此时state=0,因此可以返回

countDown方法可以用在任何地方,这里的初始值N,可以是N个线程执行完毕之后调用N次countDown方法,也可以是1个线程里的N次调用countDown方法。

CountDownLatch一般用来确保某些活动直到其他活动都完成才继续执行,比如:

  1. 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
  2. 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
  3. 等待直到某个操作所有参与者都准备就绪再继续执行。

CountDownLatch闭锁源码解析(基于jdk11)的更多相关文章

  1. String,StringBuffer和StringBuilder源码解析[基于JDK6]

    最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...

  2. 【Java并发集合】ConcurrentHashMap源码解析基于JDK1.8

    concurrentHashMap(基于jdk1.8) 类注释 所有的操作都是线程安全的,我们在使用时无需进行加锁. 多个线程同时进行put.remove等操作时并不会阻塞,可以同时进行,而HashT ...

  3. Spring源码解析-基于注解依赖注入

    在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...

  4. Java 集合系列13之 WeakHashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对WeakHashMap进行学习.我们先对WeakHashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用WeakHashMap.第1部分 WeakHashMap介绍 ...

  5. Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  6. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解Arra ...

  7. Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

    概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...

  8. Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

  9. Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSe ...

  10. Java 集合系列17之 TreeSet详细介绍(源码解析)和使用示例

    概要 这一章,我们对TreeSet进行学习.我们先对TreeSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeSet.内容包括:第1部分 TreeSet介绍第2部分 TreeSe ...

随机推荐

  1. Kubernetes 监控--Alertmanager

    前面我们学习 Prometheus 的时候了解到 Prometheus 包含一个报警模块,就是我们的 AlertManager,Alertmanager 主要用于接收 Prometheus 发送的告警 ...

  2. Prometheus与服务发现

    这种按需的资源使用方式对于监控系统而言就意味着没有了一个固定的监控目标,所有的监控对象(基础设施.应用.服务)都在动态的变化.对于Prometheus这一类基于Pull模式的监控系统,显然也无法继续使 ...

  3. Redash中文版安装问题大全

    Redash的安装比较复杂,由于系统环境组件版本不同,可能会出现这样那样的问题,我们把安装过程中常见问题记录如下: 1.git clone 经常提示:RPC失败,远端意外挂断.过早的文件结束符.ind ...

  4. 前端三件套 HTML+CSS+JS基础知识内容笔记

    HTML基础 目录 HTML基础 HTML5标签 doctype 标签 html标签 head标签 meta标签 title标签 body标签 文本和超链接标签 标题标签 段落标签 换行标签 水平标签 ...

  5. 前端程序员学习 Golang gin 框架实战笔记之一开始玩 gin

    原文链接 我是一名五六年经验的前端程序员,现在准备学习一下 Golang 的后端框架 gin. 以下是我的学习实战经验,记录下来,供大家参考. https://github.com/gin-gonic ...

  6. value中放vue的参数

    <input type="text" v-model="userInfo.name"/>

  7. 220722 T1 分树 (模拟)

    dfs一遍求出以每个节点为根的子树大小,然后枚举n的约数,对于每个约数i,统计sz[ ]是i的倍数的有多少个(开桶统计),如果有n/i个则答案+1. 这道题也就是个结论题,画图分析一下.复杂度O(n* ...

  8. 成功解决:Can‘t find Python executable “python“, you can set the PYTHON env variable.

    今天跑公司新项目的时候.运行前端vue.报了一个关于python的错误.就离谱 1.问题报错全部代码 actual version of core-js. npm ERR! code 1 npm ER ...

  9. spring boot+vue前后端项目的分离(我的第一个前后端分离项目)

    文章目录 1.前端vue的搭建 2.后端项目的构建 pom文件中引入的jar包 yml文件用来配置连接数据库和端口的设置 application.property进行一些整合 controller层( ...

  10. 【.NET 6】RabbitMQ延迟消费指南

    背景 最近遇到一个比较特殊需求,需要修改一个的RabbitMQ消费者,以实现在消费某种特定的类型消息时,延迟1小时再处理,几个需要注意的点: 延迟是以小时为单位 不是所有消息都延迟消费,只延迟特定类型 ...