返回顶部
首页 > 资讯 > 精选 >CountDownLatch和Atomic原子操作类源码分析
  • 598
分享到

CountDownLatch和Atomic原子操作类源码分析

2023-06-29 11:06:26 598人浏览 薄情痞子
摘要

本篇内容主要讲解“CountDownLatch和Atomic原子操作类源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“CountDownLatch和Atomic原子操作类源码分析”吧!引导

本篇内容主要讲解“CountDownLatch和Atomic原子操作类源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“CountDownLatch和Atomic原子操作类源码分析”吧!

    引导语

    本小节和大家一起来看看 CountDownLatch 和 Atomic 打头的原子操作类,CountDownLatch 的源码非常少,看起来比较简单,但 CountDownLatch 的实际应用却不是很容易;Atomic 原子操作类就比较好理解和应用,接下来我们分别来看一下。

    1、CountDownLatch

    CountDownLatch 中文有的叫做计数器,也有翻译为计数,其最大的作用不是为了加锁,而是通过计数达到等待的功能,主要有两种形式的等待:

    • 让一组线程在全部启动完成之后,再一起执行(先启动的线程需要阻塞等待后启动的线程,直到一组线程全部都启动完成后,再一起执行);

    • 主线程等待另外一组线程都执行完成之后,再继续执行。

    我们会举一个示例来演示这两种情况,但在这之前,我们先来看看 CountDownLatch 的底层源码实现,这样就会清晰一点,不然一开始就来看示例,估计很难理解。

    CountDownLatch 有两个比较重要的 api,分别是 await 和 countDown,管理着线程能否获得锁和锁的释放(也可以称为对 state 的计数增加和减少)。

    1.1、await

    await 我们可以叫做等待,也可以叫做加锁,有两种不同入参的方法,源码如下:

    public void await() throws InterruptedException {    sync.acquireSharedInterruptibly(1);}// 带有超时时间的,最终都会转化成毫秒public boolean await(long timeout, TimeUnit unit)    throws InterruptedException {    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}

    两个方法底层使用的都是 sync,sync 是一个同步器,是 CountDownLatch 的内部类实现的,如下:

    private static final class Sync extends AbstractQueuedSynchronizer {}

    可以看出来 Sync 继承了 AbstractQueuedSynchronizer,具备了同步器的通用功能。

    无参 await 底层使用的是 acquireSharedInterruptibly 方法,有参的使用的是 tryAcquireSharedNanos 方法,这两个方法都是 AQS 的方法,底层实现很相似,主要分成两步:

    使用子类的 tryAcquireShared 方法尝试获得锁,如果获取了锁直接返回,获取不到锁走 2;

    获取不到锁,用 node 封装一下当前线程,追加到同步队列的尾部,等待在合适的时机去获得锁。

    第二步是 AQS 已经实现了,第一步 tryAcquireShared 方法是交给 Sync 实现的,源码如下:

    // 如果当前同步器的状态是 0 的话,表示可获得锁protected int tryAcquireShared(int acquires) {    return (getState() == 0) ? 1 : -1;}

    获得锁的代码也很简单,直接根据同步器的 state 字段来进行判断,但还是有两点需要注意一下:

    获得锁时,state 的值不会发生变化,像 ReentrantLock 在获得锁时,会把 state + 1,但 CountDownLatch 不会;

    CountDownLatch 的 state 并不是 AQS 的默认值 0,而是可以赋值的,是在 CountDownLatch 初始化的时候赋值的,

    代码如下:

    // 初始化,count 代表 state 的初始化值public CountDownLatch(int count) {    if (count < 0) throw new IllegalArgumentException("count < 0");    // new Sync 底层代码是 state = count;    this.sync = new Sync(count);}

    这里的初始化的 count 和一般的锁意义不太一样,count 表示我们希望等待的线程数,在两种不同的等待场景中,count 有不同的含义:

    让一组线程在全部启动完成之后,再一起执行的等待场景下, count 代表一组线程的个数;

    主线程等待另外一组线程都执行完成之后,再继续执行的等待场景下,count 代表一组线程的个数。

    所以我们可以把 count 看做我们希望等待的一组线程的个数,可能我们是等待一组线程全部启动完成,可能我们是等待一组线程全部执行完成。

    1.2、countDown

    countDown 中文翻译为倒计时,每调用一次,都会使 state 减一,底层调用的方法如下:

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

    releaseShared 是 AQS 定义的方法,方法主要分成两步:

    尝试释放锁(tryReleaseShared),锁释放失败直接返回,释放成功走2 

    释放当前节点的后置等待节点。

    第二步 AQS 已经实现了,第一步是 Sync 实现的,我们一起来看下 tryReleaseShared 方法的实现源码:

    // 对 state 进行递减,直到 state 变成 0;// state 递减为 0 时,返回 true,其余返回 falseprotected boolean tryReleaseShared(int releases) {    // 自旋保证 CAS 一定可以成功    for (;;) {        int c = getState();        // state 已经是 0 了,直接返回 false        if (c == 0)            return false;        // 对 state 进行递减        int nextc = c-1;        if (compareAndSetState(c, nextc))            return nextc == 0;    }}

    从源码中可以看到,只有到 count 递减到 0 时,countDown 才会返回 true。

    1.3、示例

    看完 CountDownLatch 两个重要 API 后,我们来实现文章开头说的两个功能:

    让一组线程在全部启动完成之后,再一起执行;

    主线程等待另外一组线程都执行完成之后,再继续执行。

    代码在 CountDownLatchDemo 类中,大家可以调试看看,源码如下:

    public class CountDownLatchDemo {   // 线程任务  class Worker implements Runnable {    // 定义计数锁用来实现功能 1    private final CountDownLatch startSignal;    // 定义计数锁用来实现功能 2    private final CountDownLatch doneSignal;     Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {      this.startSignal = startSignal;      this.doneSignal = doneSignal;    }// 子线程做的事情    public void run() {      try {        System.out.println(Thread.currentThread().getName()+" begin");        // await 时有两点需要注意:await 时 state 不会发生变化,2:startSignal 的state初始化是 1,所以所有子线程都是获取不到锁的,都需要到同步队列中去等待,达到先启动的子线程等待后面启动的子线程的结果        startSignal.await();        doWork();        // countDown 每次会使 state 减一,doneSignal 初始化为 9,countDown 前 8 次执行都会返回 false (releaseShared 方法),执行第 9 次时,state 递减为 0,会 countDown 成功,表示所有子线程都执行完了,会释放 await 在 doneSignal 上的主线程        doneSignal.countDown();        System.out.println(Thread.currentThread().getName()+" end");      } catch (InterruptedException ex) {      } // return;    }     void doWork() throws InterruptedException {      System.out.println(Thread.currentThread().getName()+"sleep 5s …………");      Thread.sleep(5000l);    }  }   @Test  public void test() throws InterruptedException {    // state 初始化为 1 很关键,子线程是不断的 await,await 时 state 是不会变化的,并且发现 state 都是 1,所有线程都获取不到锁    // 造成所有线程都到同步队列中去等待,当主线程执行 countDown 时,就会一起把等待的线程给释放掉    CountDownLatch startSignal = new CountDownLatch(1);    // state 初始化成 9,表示有 9 个子线程执行完成之后,会唤醒主线程    CountDownLatch doneSignal = new CountDownLatch(9);     for (int i = 0; i < 9; ++i) // create and start threads    {      new Thread(new Worker(startSignal, doneSignal)).start();    }    System.out.println("main thread begin");    // 这行代码唤醒 9 个子线程,开始执行(因为 startSignal 锁的状态是 1,所以调用一次 countDown 方法就可以释放9个等待的子线程)    startSignal.countDown();    // 这行代码使主线程陷入沉睡,等待 9 个子线程执行完成之后才会继续执行(就是等待子线程执行 doneSignal.countDown())    doneSignal.await();               System.out.println("main thread end");  }}执行结果:Thread-0 beginThread-1 beginThread-2 beginThread-3 beginThread-4 beginThread-5 beginThread-6 beginThread-7 beginThread-8 beginmain thread beginThread-0sleep 5s …………Thread-1sleep 5s …………Thread-4sleep 5s …………Thread-3sleep 5s …………Thread-2sleep 5s …………Thread-8sleep 5s …………Thread-7sleep 5s …………Thread-6sleep 5s …………Thread-5sleep 5s …………Thread-0 endThread-1 endThread-4 endThread-3 endThread-2 endThread-8 endThread-7 endThread-6 endThread-5 endmain thread end

    从执行结果中,可以看出已经实现了以上两个功能,实现比较绕,大家可以根据注释,debug 看一看。

    2、Atomic 原子操作类

    Atomic 打头的原子操作类有很多,涉及到 Java 常用的数字类型的,基本都有相应的 Atomic 原子操作类,如下图所示:

    CountDownLatch和Atomic原子操作类源码分析

    Atomic 打头的原子操作类,在高并发场景下,都是线程安全的,我们可以放心使用。

    我们以 AtomicInteger 为例子,来看下主要的底层实现:

    private volatile int value;// 初始化public AtomicInteger(int initialValue) {    value = initialValue;}// 得到当前值public final int get() {    return value;}// 自增 1,并返回自增之前的值    public final int getAndIncrement() {    return unsafe.getAndAddInt(this, valueOffset, 1);}// 自减 1,并返回自增之前的值    public final int getAndDecrement() {    return unsafe.getAndAddInt(this, valueOffset, -1);}

     从源码中,我们可以看到,线程安全的操作方法,底层都是使用 unsafe 方法实现,以上几个 unsafe 方法不是使用 Java 实现的,都是线程安全的。

    AtomicInteger 是对 int 类型的值进行自增自减,那如果 Atomic 的对象是个自定义类怎么办呢,Java 也提供了自定义对象的原子操作类,叫做 AtomicReference。AtomicReference 类可操作的对象是个泛型,所以支持自定义类,其底层是没有自增方法的,操作的方法可以作为函数入参传递,源码如下:

    // 对 x 执行 accumulatorFunction 操作// accumulatorFunction 是个函数,可以自定义想做的事情// 返回老值public final V getAndAccumulate(V x,                                BinaryOperator<V> accumulatorFunction) {    // prev 是老值,next 是新值    V prev, next;    // 自旋 + CAS 保证一定可以替换老值    do {        prev = get();        // 执行自定义操作        next = accumulatorFunction.apply(prev, x);    } while (!compareAndSet(prev, next));    return prev;}

    到此,相信大家对“CountDownLatch和Atomic原子操作类源码分析”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    --结束END--

    本文标题: CountDownLatch和Atomic原子操作类源码分析

    本文链接: https://lsjlt.com/news/324573.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • CountDownLatch和Atomic原子操作类源码分析
      本篇内容主要讲解“CountDownLatch和Atomic原子操作类源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“CountDownLatch和Atomic原子操作类源码分析”吧!引导...
      99+
      2023-06-29
    • CountDownLatch和Atomic原子操作类源码解析
      目录引导语1、CountDownLatch1.1、await1.2、countDown1.3、示例2、Atomic原子操作类3、总结引导语 本小节和大家一起来看看 CountDown...
      99+
      2024-04-02
    • Java原子操作类源码分析
      这篇文章主要介绍“Java原子操作类源码分析”,在日常操作中,相信很多人在Java原子操作类源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java原子操作类源码分析”的疑惑有所帮助!接下来,请跟着小编...
      99+
      2023-06-30
    • Java多线程Atomic包操作原子变量与原子类的示例分析
      这篇文章主要介绍Java多线程Atomic包操作原子变量与原子类的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、何谓Atomic?Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的...
      99+
      2023-05-30
      java
    • GO的锁和原子操作实例分析
      本篇内容介绍了“GO的锁和原子操作实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!锁是什么锁 是用于解决隔离性的一种机制某个协程(线程...
      99+
      2023-07-05
    • redis原子操作实例分析
      这篇“redis原子操作实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“redis原...
      99+
      2024-04-02
    • C#原子操作实例分析
      这篇文章主要讲解了“C#原子操作实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#原子操作实例分析”吧!知识点竞争条件当两个或两个以上的线程访问共享数据,并且尝试同时改变它时,就发生...
      99+
      2023-06-29
    • python文件读写操作源码分析
      本篇内容介绍了“python文件读写操作源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!文件写操作的案例# 打开文件(只写模...
      99+
      2023-07-05
    • Netty源码分析NioEventLoop执行select操作入口
      目录select操作的入口NioEventLoop的run方法轮询io事件rebuildSelector()方法分析完了selector的创建和优化的过程, 这一小节分析select...
      99+
      2024-04-02
    • 如何解析Python源码分析的相关操作步骤
      今天就跟大家聊聊有关如何解析Python源码分析的相关操作步骤,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Python是一种动态的脚本语言。具体的我就不多介绍了,源代码链接在这里:...
      99+
      2023-06-17
    • Java详解HashMap实现原理和源码分析
      目录学习要点:1、什么是HashMap?2、HashMap的特性3、HashMap的数据结构4、HashMap初始化操作4.1、成员变量4.2、 构造方法5、Jdk8中HashMap...
      99+
      2024-04-02
    • OpenMP创建线程中的锁及原子操作性能分析
      这篇文章主要讲解了“OpenMP创建线程中的锁及原子操作性能分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“OpenMP创建线程中的锁及原子操作性能分析”...
      99+
      2024-04-02
    • C++双向链表的增删查改操作方法源码分析
      这篇“C++双向链表的增删查改操作方法源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++双向链表的增删查改操作方法...
      99+
      2023-07-05
    • Go map底层实现、扩容规则和特性分类源码分析
      这篇文章主要介绍“Go map底层实现、扩容规则和特性分类源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go map底层实现、扩容规则和特性分类源码分析”文章能帮助大家解...
      99+
      2023-07-05
    • Java之Spring Bean作用域和生命周期源码分析
      这篇文章主要讲解了“Java之Spring Bean作用域和生命周期源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java之Spring Bean作用域和生命周期...
      99+
      2023-07-05
    • Rxjava源码分析&实践(六)【实践环节:map操作符功能实现】
      Rxjava源码分析&实践系列文章目录 Rxjava源码分析&实践(一)【RxJava的基本使用】 Rxjava源码分析&实践(二)【RxJava基本原理分析之构建流】 Rxjava源码分析&实践(...
      99+
      2023-09-02
      rxjava android
    • Java NIO Path接口和Files类配合操作文件的示例分析
      这篇文章将为大家详细讲解有关Java NIO Path接口和Files类配合操作文件的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Path接口Path表示的是一个目录名序列,其后还可以跟着一个文...
      99+
      2023-05-30
      java nio
    • 基于MongoDB数据库中数据类型和$type操作符的示例分析
      这篇文章将为大家详细讲解有关基于MongoDB数据库中数据类型和$type操作符的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。前面的话  本文将详细介绍Mong...
      99+
      2024-04-02
    • Android系统 Settings数据库读写操作和数据监听变化原理分析
      在Android系统当中,系统设置保存着全局性、系统级别的用户编好设置,比如像飞行模式开关、是否开启手机静音模式时震动、屏幕休眠时长等状态值。这些用户偏好的设置很多就保存在SettingsProvider中,在Android 6.0及以后版...
      99+
      2023-09-02
      android
    • 操作系统内存分配和释放:深入解析计算机资源的精彩世界
      内存分配和释放是计算机科学中最重要的概念之一,也是操作系统设计的基础。内存分配是指将内存空间分配给进程或线程使用,内存释放则是将不再使用的内存空间释放回系统。本文将深入探讨内存分配和释放背后的原理,通过演示代码和实际示例,帮助读者更好地...
      99+
      2024-02-12
      内存分配 内存释放 操作系统 计算机科学 进程 线程
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作