深度剖析JUC中LongAdder类源码

文章目录

    • 1.诞生背景
    • 2.LongAdder核心思想
    • 3.底层实现:
    • 4.额外补充

1.诞生背景

LongAdder是JDK8新增的一个原子操作类,和AtomicLong扮演者同样的角色,由于采用AtomicLong 保证多线程数据同步,高并发场景下会导致大量线程同时竞争更新一个原子变量,容易造成大量线程竞争失败后,无线循环不断自旋尝试CAS,极大浪费CPU资源为了解决这个循环自旋尝试CAS极大占用CPU资源的问题,JDK大佬就创造了LongAdder类

2.LongAdder核心思想

将一个变量拆分成多个变量,高并发场景下让多个线程竞争获取多个资源,用以减少竞争资源冲突,从而提升性能。

本质就是一种分段锁思想,将一个变量分成多段,多线程并发下获取不同分段对象cell不会发生竞争,有效避免大量线程自旋竞争CAS

3.底层实现:

下面结合LongAdder的结构,add()sum() 方法对类底层执行进行剖析。
LongAdder是继承于Striped64。其中比较重要的四个参数在下图列出。

// 继承Striped64.
public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;
}
// Striped64 类中四个实例变量
    /** Number of CPUS, to place bound on table size */
    // 当前机器CPU数目
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * Table of cells. When non-null, size is a power of 2.
     */
     // 用于存储变量的数组,初始size为2,采用2倍扩容,因为length参与了线程获取cell对象时索引计算
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
     // 变量基数
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     */
     // 用于判断是否发生cell竞争状态标识,1表示存在获取cell线程。
    transient volatile int cellsBusy;

cells数组是用来存储变量值的一部分的集合。Cell结构如下:以JDK21为例子

    /**
     * Padded variant of AtomicLong supporting only raw accesses plus CAS.
     *
     * JVM intrinsics note: It would be possible to use a release-only
     * form of CAS here, if it were provided.
     */
    @jdk.internal.vm.annotation.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return VALUE.weakCompareAndSetRelease(this, cmp, val);
        }
        final void reset() {
            VALUE.setVolatile(this, 0L);
        }
        final void reset(long identity) {
            VALUE.setVolatile(this, identity);
        }
        final long getAndSet(long val) {
            return (long)VALUE.getAndSet(this, val);
        }

        // VarHandle mechanics
        private static final VarHandle VALUE;
        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                VALUE = l.findVarHandle(Cell.class, "value", long.class);
            } catch (ReflectiveOperationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    }

@jdk.internal.vm.annotation.Contended 注解是用以字节填充,用来避免伪共享。这个伪共享在上一篇剖析AQS源码中有讲。点击查看

这个Cell结构很简单,就是使用value变量来存储值。

接着看看sum() 方法:

    /**
     * Returns the current sum.  The returned value is <em>NOT</em> an
     * atomic snapshot; invocation in the absence of concurrent
     * updates returns an accurate result, but concurrent updates that
     * occur while the sum is being calculated might not be
     * incorporated.
     *
     * @return the sum
     */
    public long sum() {
        Cell[] cs = cells;
        long sum = base;
        if (cs != null) {
            for (Cell c : cs)
                if (c != null)
                    sum += c.value;
        }
        return sum;
    }

代码逻辑很简单,就是把base的值和cells数组里的值求和,这个就是LongAdder实际值

继续看add() 方法,这个是整个类最关键的方法:

    /**
     * Adds the given value.
     *
     * @param x the value to add
     */
    public void add(long x) {
		// b为基础值, v为存储当前线程被分配到具体某个cell的value。
		/** 
		m 当前cell的长度-1 ,
		由于cell的长度是2的幂数,因此结构必然是`...1111`结尾,
		index & m 就是用来计算当前线程竞争获取对象cell在cells的位置
		!(uncontended = c.cas(v = c.value, v + x) 
		cell对象cas失败走longAccumulate(x, null, uncontended, index)逻辑。
        **/
        Cell[] cs; long b, v; int m; Cell c;
        if ((cs = cells) != null || !casBase(b = base, b + x)) {
            int index = getProbe();
            boolean uncontended = true;
            if (cs == null || (m = cs.length - 1) < 0 ||
                (c = cs[index & m]) == null ||
                !(uncontended = c.cas(v = c.value, v + x)))
                longAccumulate(x, null, uncontended, index);
        }
    }
    /**
     * Returns the probe value for the current thread.
     * Duplicated from ThreadLocalRandom because of packaging restrictions.
     */
    static final int getProbe() {
        return (int) THREAD_PROBE.get(Thread.currentThread());
    }

1.介绍add方法前,先对getProbe()方法进行简要说明,这个可以理解为根据当前线程获取一个唯一id用来计算当前线程参与竞争Cell对象cells数组中索引位置
2.假定一个线程就是一个用户,cells中是一个窗口服务列表,cells中的每个cell实例是一个窗口,相关运行流程图如下:

在这里插入图片描述
3.接着来分析下longAccumulate 这个方法:

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended, int index) {
        if (index == 0) {
            ThreadLocalRandom.current(); // force initialization
            index = getProbe();
            wasUncontended = true;
        }
        // 默认冲突为false
        for (boolean collide = false;;) {       // True if last slot nonempty
            Cell[] cs; Cell c; int n; long v;
            if ((cs = cells) != null && (n = cs.length) > 0) {
            	// 当前cell对象为空
                if ((c = cs[(n - 1) & index]) == null) {
                	// cellsBusy 为0 此时可以通过自旋竞争获取锁。
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        // cas方式加锁。这个锁在创建对象和扩容时需要加锁。
                        if (cellsBusy == 0 && casCellsBusy()) {
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                // 二次检查。确保对象索引位置为null在执行赋值操作。
                                // 这个和懒加载单例模式DoubleCheck 思想一直。
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & index] == null) {
                                    //  将新建的cell对象r赋值给指定位置。
                                    rs[j] = r;
                                    break;
                                }
                            } finally {
                            	 // 锁释放
                                cellsBusy = 0;
                            }
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                // cas执行失败 设置true重新再执行
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // 当前cell 存在 则执行CAS,如果方法fn为null则执行加法操作此时就是LongAdder
                // 如果传了函数,则调用自定义函数fn。
                else if (c.cas(v = c.value,
                               (fn == null) ? v + x : fn.applyAsLong(v, x)))
                    break;
                //  如果当前Cell数组元素个数大于CPU数或者已经完成扩容,则冲突为false。
                else if (n >= NCPU || cells != cs)
                    collide = false;            // At max size or stale
                // 如果以上判断均不满足,则是存在冲突的。设置为true。
                else if (!collide)
                    collide = true;
                // 如果当前n没有到达cpu个数且存在冲突。
                // 尝试扩容。cas机制扩容加锁,避免多个线程都进行扩容操作。
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == cs)        // Expand table unless stale
                            cells = Arrays.copyOf(cs, n << 1); // 必须是2倍扩容
                    } finally {
                    	// 锁释放
                        cellsBusy = 0;
                    }
                    // 重新设置冲突为false。
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                index = advanceProbe(index);
            }
            // cell数组初始化判断逻辑。
            else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
                try {                           // Initialize table
                    if (cells == cs) {
                        Cell[] rs = new Cell[2];
                        rs[index & 1] = new Cell(x);
                        cells = rs;
                        break;
                    }
                } finally {
                    cellsBusy = 0;
                }
            }
            // Fall back on using base
            else if (casBase(v = base,
                             (fn == null) ? v + x : fn.applyAsLong(v, x)))
                break;
        }
    }

index ==0 说明 getProbe() 方法为0,(int)
THREAD_PROBE.get(Thread.currentThread())=0,
则说明当前线程threadLocalRandomProbe=0, 这个是通过反射实现值获取。

在这里插入图片描述
再看Thread类中,关于threadLocalRandomProbe注释,说明ThreadLocalRandom 对象没有初始化,因此才需执行初始化操作。也就是ThreadLocalRandom.current()这个方法。

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    // 初始化后不能为0。
    int threadLocalRandomProbe;

由于longAccumulate 代码量太大,关于运行情况,注释都写在代码上。 相信大家都能看懂。

4.额外补充

1.LongAdder 和LongAccumulate 之间的关系,longAccumulate 是通用累计计算器,不仅可以实现累加,还可以根据用户自定义函数来实现累计功能,LongAdder 是其中一个特例,相当于就是一个longAccumulate 默认实现。
2.Cell数组的个数和CPU相等,此时性能能得到最大发挥。
3.Cell数组占用内存相对较大,一开始是null,只有在使用到Cell数组才会创建,惰性加载/懒加载方式。
4。应用场景:适用于高并发累计统计计数场景,不适用于单线程、以及多线程下实时获取精准数据的情况。
核心思想分段锁,采用空间换时间的策略,来提升高并发下统计计数的效率。大多数性能优化,都是空间换时间、时间换空间这两者之间做权衡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/913064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…

树形结构数据

树形结构数据 树形结构数据是一种基础且强大的数据结构&#xff0c;广泛应用于计算机科学和软件开发的各个领域。它模拟了自然界中树的层级关系&#xff0c;通过节点和它们之间的连接来组织数据。在本文中&#xff0c;我们将深入探讨树形结构数据的概念、特点、类型以及它们在…

dell服务器安装ESXI8

1.下载镜像在官网 2.打开ipmi&#xff08;idrac&#xff09;&#xff0c;将esxi镜像挂载&#xff0c;然后服务器开机 3.进入bios设置cpu虚拟化开启&#xff0c;进入boot设置启动选项为映像方式 4..进入安装引导界面3.加载完配置进入安装 系统提示点击继 5.选择安装磁盘进行…

信息安全数学基础(46)域和Galois理论

域详述 定义&#xff1a; 域是一个包含加法、减法、乘法和除法&#xff08;除数不为零&#xff09;的代数结构&#xff0c;其中加法和乘法满足交换律、结合律&#xff0c;并且乘法对加法满足分配律。同时&#xff0c;域中的元素&#xff08;通常称为数&#xff09;在加法和乘法…

Windows端口占用/Java程序启动失败-进程占用的问题解决

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Python酷库之旅-第三方库Pandas(204)

目录 一、用法精讲 951、pandas.IntervalIndex.values属性 951-1、语法 951-2、参数 951-3、功能 951-4、返回值 951-5、说明 951-6、用法 951-6-1、数据准备 951-6-2、代码示例 951-6-3、结果输出 952、pandas.IntervalIndex.from_arrays类方法 952-1、语法 952…

AndroidStudio-文本显示

一、设置文本的内容 1.方式&#xff1a; &#xff08;1&#xff09;在XML文件中通过属性&#xff1a;android:text设置文本 例如&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.andr…

微星爆破弹ddr4wifi接线梳理研究

主板(微星爆破弹ddr4 wifi) mac用久了&#xff0c;windows的键盘都有点不习惯了。 理清了这些接口都是干啥的&#xff0c;接线就非常简单了。

机器视觉基础—双目相机

机器视觉基础—双目相机与立体视觉 双目相机概念与测量原理 我们多视几何的基础就在于是需要不同的相机拍摄的同一个物体的视场是由重合的区域的。通过下面的这种几何模型的目的是要得到估计物体的长度&#xff0c;或者说是离这个相机的距离。&#xff08;深度信息&#xff09…

【GPTs】EmojiAI:轻松生成趣味表情翻译

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;EmojiAI主要功能适用场景优点缺点 &#x1f4af;小结 &#x1f4af;GPTs指令 中文翻译&#xff1a; 此 GPT 的主要角色是为英文文本提供幽默…

「C/C++」C/C++STL 之 push_back 和 emplace_back 的区别

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明目…

【Golang】Go语言教程

Go语言教程 文章目录 Go语言教程一、Go语言教程二、Go语言特色三、Go语言用途四、第一个Go程序六、运行代码的两种方式七、go run和go buil的区别7.1、go run7.2、Go build 一、Go语言教程 Go全称Golang Go是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变…

揭秘云计算 | 2、业务需求推动IT发展

揭秘云计算 | 1、云从哪里来&#xff1f;-CSDN博客https://blog.csdn.net/Ultipa/article/details/143430941?spm1001.2014.3001.5502 书接上文&#xff1a; 过去几十年间IT行业从大型主机过渡到客户端/服务器&#xff0c;再过渡到现如今的万物互联&#xff0c;IT可把控的资…

Tencent Hunyuan3D

一、前言 腾讯于2024年11月5日正式开源了最新的MoE模型“混元Large”以及混元3D生成大模型“Hunyuan3D-1.0”&#xff0c;支持企业及开发者在精调、部署等不同场景下的使用需求。 GitHub - Tencent/Hunyuan3D-1 二、技术与原理 Hunyuan3D-1.0 是一款支持文本生成3D&#xff08;…

WPF在MVVM模式下怎么实现导航功能

在mvvm的模式下wpf通过frame实现页面跳转_哔哩哔哩_bilibili 视频讲解同步可观看 如下图&#xff0c;我们要实现点击左侧的菜单&#xff0c;在右侧展示不同的页面 实现代码如下&#xff1a; 一、如何从主窗体跳转到页面。 1、在mainwindow.xaml的菜单栏代码里加入如下代码 …

SpringBoot整合Sharding-JDBC实现读写分离

SpringBoot整合Sharding-JDBC实现读写分离 Sharding-JDBC实现读写分离&#xff0c;记得先要实现数据库的主从结构先。 1、Sharding-JDBC 简介 Sharding-JDBC 是的分布式数据库中间件解决方案。Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划 中)是 3 款相互独立的…

洛谷每日一题——P1036 [NOIP2002 普及组] 选数、P1045 [NOIP2003 普及组] 麦森数(高精度快速幂)

P1036 [NOIP2002 普及组] 选数 题目描述 [NOIP2002 普及组] 选数 - 洛谷 运行代码 #include <stdio.h> int n, k, a[25], t; int ss(int b) {int i;if (b < 2)return 0;for (i 2; i * i < b; i)if (b % i 0)return 0;return 1; } void dfs(int num, int sum, …

从零开始 blender插件开发

blender 插件开发 文章目录 blender 插件开发环境配置1. 偏好设置中开启相关功能2. 命令行打开运行脚本 API学习专有名词1. bpy.data 从当前打开的blend file中&#xff0c;加载数据。2. bpy.context 可用于获取活动对象、场景、工具设置以及许多其他属性。3. bpy.ops 用户通常…

【若依框架】代码生成详细教程,15分钟搭建Springboot+Vue3前后端分离项目,基于Mysql8数据库和Redis5,管理后台前端基于Vue3和Element Plus,开发小程序数据后台

今天我们来借助若依来快速的搭建一个基于springboot的Java管理后台&#xff0c;后台网页使用vue3和 Element Plus来快速搭建。这里我们可以借助若依自动生成Java和vue3代码&#xff0c;这就是若依的强大之处&#xff0c;即便你不会Java和vue开发&#xff0c;只要跟着石头哥也可…