深度剖析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 mechanicsprivate 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 initializationindex = getProbe();wasUncontended = true;}// 默认冲突为falsefor (boolean collide = false;;) {       // True if last slot nonemptyCell[] 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 CellCell r = new Cell(x);   // Optimistically create// cas方式加锁。这个锁在创建对象和扩容时需要加锁。if (cellsBusy == 0 && casCellsBusy()) {try {               // Recheck under lockCell[] 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 failwasUncontended = 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 stalecells = 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 tableif (cells == cs) {Cell[] rs = new Cell[2];rs[index & 1] = new Cell(x);cells = rs;break;}} finally {cellsBusy = 0;}}// Fall back on using baseelse 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。应用场景:适用于高并发累计统计计数场景,不适用于单线程、以及多线程下实时获取精准数据的情况。
核心思想分段锁,采用空间换时间的策略,来提升高并发下统计计数的效率。大多数性能优化,都是空间换时间、时间换空间这两者之间做权衡

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Python(PySimpleGUI 库)

PySimpleGUI 是一个用于简化 GUI 编程的 Python 包&#xff0c;它封装了多种底层 GUI 框架&#xff08;如 tkinter、Qt、WxPython 等&#xff09;&#xff0c;提供了简单易用的 API。PySimpleGUI 包含了大量的控件&#xff08;也称为小部件或组件&#xff09;&#xff0c;这些控…

LangChain学习心得总结

大模型开发遇到的问题及langchain框架学习 背景&#xff1a; 1、微场景间跳转问题&#xff0c;无法实现微场景随意穿插 2、大模型幻读&#xff08;推荐不存在的产品、自己发挥&#xff09; 3、知识库检索&#xff0c;语义匹配效果较差&#xff0c;匹配出的结果和客户表述的…

Linux基础(十二)——文件与文件系统的压缩、打包和备份

文件与文件系统的压缩、打包和备份 1.压缩1.1 压缩方法及其后缀1.2 gzip1.3 bzip21.4 xz 2.打包3.XFS文件系统备份与还原4.镜像文件创建&#xff08;mkisofs&#xff09; 1.压缩 1.1 压缩方法及其后缀 我们知道在 Linux 下面的扩展名是没有什么很特殊的意义的&#xff0c; 不…

简简单单的UDP

前言 上一篇了解了TCP的三次握手过程&#xff0c;目的、以及如何保证可靠性、序列号与ACK的作用&#xff0c;最后离开的时候四次挥手的内容&#xff0c;这还只是TCP内容中的冰山一角&#xff0c;是不是觉得TCP这个协议非常复杂&#xff0c;这一篇我们来了解下传输层另外一个协…

MLMs之OmniGen:OmniGen(统一图像生成模型)的简介、安装和使用方法、案例应用之详细攻略

MLMs之OmniGen&#xff1a;OmniGen(统一图像生成模型)的简介、安装和使用方法、案例应用之详细攻略 导读&#xff1a;这篇论文介绍了OmniGen&#xff0c;一个用于统一图像生成的扩散模型。论文的核心要点可以总结如下&#xff1a; >> 背景痛点&#xff1a; ● 图像生成领…

LeetCode 143.重排链表

题目&#xff1a; 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际…

Linux进程信号(信号的产生)

目录 什么是信号&#xff1f; 信号的产生 信号产生方式1&#xff1a;键盘 前台进程 后台进程 查看信号 signal系统调用 案例 理解进程记录信号 软件层面 硬件层面 信号产生方式2:指令 信号产生方式3:系统调用 kill系统调用 案例 其他产生信号的函数调用 1.rais…

【C++】STL— stack的常见用法和模拟实现

目录 1、stack的介绍 2、stack的使用 构造一个空栈 stack的简单接口应用 3、stack的模拟实现 4、栈的相关题目 4.1 最小栈 4.1.2思路 4.1.3 实现代码 4.2 栈的压入、弹出序列 4.2.2 思路 4.2.3程序实现 1、stack的介绍 在C中&#xff0c;stack是一种标准模板库&am…

神书《从零构建大模型》分享,尚未发布,GitHub标星22k!!

《从零构建大模型》是一本即将于今年10月底发布的书籍&#xff0c;github已经吸引了惊人的21.7k标星&#xff01;作者是威斯康星大学麦迪逊分校的终身教授&#xff0c;在GitHub、油管、X上拥有大量粉丝&#xff0c;是一位真正的大佬。 本书免费获取地址 在本书中&#xff0…

【深度学习目标检测|YOLO算法2】YOLO家族进化史:从YOLOv1到YOLOv11的架构创新、性能优化与行业应用全解析...

【深度学习目标检测|YOLO算法2】YOLO家族进化史&#xff1a;从YOLOv1到YOLOv11的架构创新、性能优化与行业应用全解析… 【深度学习目标检测|YOLO算法2】YOLO家族进化史&#xff1a;从YOLOv1到YOLOv11的架构创新、性能优化与行业应用全解析… 文章目录 【深度学习目标检测|YOL…

动态避障-图扑自动寻路 3D 可视化

自动寻路是机器人导航的核心技术&#xff0c;其原理主要涉及机器人与环境之间的复杂信息交互与处理。在自动寻路过程中&#xff0c;机器人依靠先进的传感器系统&#xff0c;如高清摄像头、精密激光雷达和灵敏超声波装置&#xff0c;全方位感知周围环境。这些传感器能够实时捕捉…

Docker 镜像拉不动?自建 Docker Hub 加速站 解决镜像拉取失败

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 众所周知&#xff0c;6 月份的时候&#xff0c;Docker Hub 的镜像就已经无法正常拉取&#xff0c;那会随手用 Nginx 反代了一下 Docker Hub&#xff0c;建了个自用的镜像站&#xff0c;一直用到了 9 月份&…

RabbitMQ集群搭建

RabbitMQ集群搭建 1、RabbitMQ集群1.1、默认集群模式1.1.1、为什么集群不复制队列内容和状态到所有节点? 1.2、镜像集群模式 2、默认集群模式安装前准备2.1、准备3台机器2.2、启动三台机器2.3、使用xshell 连接三台机器2.4、服务器安装erlang和RabbitMQ2.5、修改三台机器的/et…

mysql-springboot netty-flink-kafka-spark(paimon)-minio

1、下载spark源码并编译 mkdir -p /home/bigdata && cd /home/bigdata wget https://archive.apache.org/dist/spark/spark-3.4.3/spark-3.4.3.tgz 解压文件 tar -zxf spark-3.4.3.tgz cd spark-3.4.3 wget https://raw.githubusercontent.com/apache/incubator-celeb…

系统安全第七次作业题目及答案

一、 1.RBAC0 RBAC1 RBAC2 RBAC3 2.属性 身份标识 3.接入访问控制 资源访问控制 网络端口和节点的访问控制 二、 1.B 2.A 3.ABE 4.BCD 5.ABC 三、 1. 答&#xff1a;基于属性的访问控制&#xff08;ABAC&#xff09;是通过对实体属性添加约束策略的方式实现主、客体之…

【GESP】C++一级真题练习(202312)luogu-B3922,小杨报数

GESP一级真题练习。为2023年12月一级认证真题。for循环和取余计算应用。 题目题解详见&#xff1a;https://www.coderli.com/gesp-1-luogu-b3922/ 【GESP】C一级真题练习(202312)luogu-B3922&#xff0c;小杨报数 | OneCoderGESP一级真题练习。为2023年12月一级认证真题。for…

国科大现代信息检索技术第一次作业

第一次作业 题目1&#xff1a;考虑以下文档 文档名内容文档1new home sales top forecasts文档2home prices rise in june文档3increase in home sales in june文档4july new home sales rise 1、画出文档集对应的词项-文档矩阵 文档1文档2文档3文档4forecasts1000home1111…

计算机视觉实验四:特征检测与匹配

特征检测与匹配 1 角点检测算法实验 1.1 实验目的与要求 &#xff08;1&#xff09;了解及掌握角点检测算法原理。 &#xff08;2&#xff09;掌握在MATLAB中角点算法的编程。 &#xff08;3&#xff09;掌握Moravec&#xff0c;Harris与SUSAN算法的差异。 1.2 实验原理及…

十八:Spring Boot 依赖(3)-- spring-boot-starter-data-jpa 依赖详解

目录 1. 理解 JPA&#xff08;Java Persistence API&#xff09; 1.1 什么是 JPA&#xff1f; 1.2 JPA 与 Hibernate 的关系 1.3 JPA 的基本注解&#xff1a;Entity, Table, Id, GeneratedValue 1.4 JPA 与数据库表的映射 2. Spring Data JPA 概述 2.1 什么是 Spring Dat…

如何用C++代码实现一颗闪烁的爱心?

要用 C 实现爱心闪烁效果&#xff0c;我们可以使用控制台输出文本&#xff0c;并通过在控制台中刷新屏幕来模拟闪烁的效果。由于 C 本身没有类似 turtle 这样的图形库&#xff0c;操作控制台输出的方式比较简单&#xff0c;主要通过字符绘制和时间延迟来实现。 这里给出一个基…