性能远超AtomicLong,LongAdder原理完全解读

【Java生产级避坑指南】9. 无锁编程实战:LongAdder如何让高并发计数性能翻倍? 摘要:某物联网平台(日均设备心跳10亿+)使用AtomicLong实现计数器时,在每秒10万+并发场景下遭遇性能瓶颈:CPU使用率飙升至90%、吞吐量停滞、GC压力剧增。经排查,根源是AtomicLong的伪共享问题——多核CPU同时修改时触发频繁缓存行无效化,导致大量CAS重试。本文从CPU缓存原理切入,详解伪共享对并发性能的影响,对比AtomicLong与LongAdder的实现差异,通过JMH压测验证:在32线程高并发下,LongAdder的QPS(3215万)是AtomicLong(84万)的37倍 阅读详情

高并发场景下可以选择 AtomicLong 原子类进行计数等操作,除了 AtomicLong,在 jdk1.8 中还提供了 LongAdder。PS:AtomicLong 在 jdk1.5 版本提供。

AtomicLong 底层使用 Unsafe 的 CAS 实现,当操作失败时,会以自旋的方式重试,直至操作成功。因此在高并发场景下,可能会有很多线程处于重试状态,徒增 CPU 的压力,造成不必要的开销。

LongAdder 提供了一个 base 值,当竞争小的情况下 CAS 更新该值,如果 CAS 操作失败,会初始化一个 cells 数组,每个线程都会通过取模的方式定位 cells 数组中的一个元素,这样就将操作单个 AtomicLong value 的压力分散到数组中的多个元素上。

通过将压力分散,LongAdder 可以提供比 AtomicLong 更好的性能。获取元素 value 值时,只要将 base 与 cells 数组中的元素累加即可。

下面是它的原理实现。

    public void increment() {
        add(1L);
    }

    public void add(long x) {
        Cell[] as;
        long b, v;
        // m = as.length -1,取模用,定位数组槽
        int m;
        Cell a;
        // 低竞争条件下,cells 为 null,此时调用 casBase(底层为 CAS 操作,类似 AtomicLong) 方法更新 base
        // PS:cells 数组为懒加载,只有在 CAS 竞争失败的情况下才会初始化
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            // as 数组为 null,或者数组 size = 0,或者计算槽后在数组中不能定位,或者 cell 对象 CAS 操作失败
            if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

LongAdder 继承自 Striped64,底层调用 Striped64.longAccumulate 方法实现。

当第一次调用 add 方法时,并不会初始化 cells 数组,而是通过 CAS 去操作 base 值,操作成功后就直接返回了。

如果 CAS 操作失败,这时会调用 longAccumulate 方法,该方法会初始化 Cell 类型的数组,后面大部分线程都会直接操作这个数组,但是仍然有部分线程会更新 base 值。

Cell 元素定义如下:

    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

多个线程操作 cells 数组原理如下:

在这里插入图片描述
图片来源 LongAdder and LongAccumulator in Java

CPU 有多级缓存,这些缓存的最小单位是缓存行(Cache Line),通常情况下一个缓存行的大小是 64 字节(并不绝对,或者是 64 的倍数)。假设现在要操作一个 long 类型的数组,long 在 Java 中占 64 bit,8 个字节,当操作数组中的一个元素时,会从主存中将该元素的附近的其他元素一起加载进缓存行,即使其他元素你不想操作。

假设两个用 volatile 修饰的元素被加载进同一个缓存行,线程 A 更新变量 A 后会将更新后的值刷新回主存,此时缓存行失效,线程 B 再去操作 B 变量只能重新从主存中读取(Cache Miss)。这就造成了伪共享(False sharing)问题。

Cell 本身没什么好讲的,仔细看一下,这个类被 @sun.misc.Contended 注解修饰,这个注解一般在写业务时用不到,但是它可以解决上面的伪共享问题。

@sun.misc.Contended 注解在 jdk1.8 中提供,保证缓存行每次缓存一个变量,剩余的空间用字节来填充。

在这里插入图片描述
图片来源 LongAdder and LongAccumulator in Java

    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        // 线程 threadLocalRandomProbe 属性值
        int h;
        if ((h = getProbe()) == 0) {
            // 初始化 Thread 的 threadLocalRandomProbe 属性值
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as;
            Cell a;
            // cells 数组大小
            int n;
            long v;
            // cells 数组已经初始化
            if ((as = cells) != null && (n = as.length) > 0) {
                // 当前线程对应数组槽的 Cell 对象为空
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        // 初始化 Cell,这里存在竞争,可能多个线程都创建了 Cell 对象
                        Cell r = new Cell(x);   // Optimistically create
                        // casCellsBusy() 更新 cellsBusy 为 1,通过 CAS 操作保证只有一个线程操作成功,cellsBusy() 方法相当于一个 spin lock
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
                                    // rs[j] 对象赋值
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            // Cell 在数组中赋值成功,跳出循环
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // CAS 操作成功,跳出循环
                else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) 
                    break;
                // 数组范围不能大于 JVM 可用核心数,cells != as 表示数组可能扩容
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                // 多个线程出现碰撞,更新 collide = true,出现碰撞后并没有直接扩容 cells 数组,而是重新 rehash,rehash CAS 失败后才会扩容
                else if (!collide) 
                       collide = true;
                // 走到这里说明出现了数组碰撞,且自旋 rehash CAS 失败,这时需要对数组扩容
                else if (cellsBusy == 0 && casCellsBusy()) { 
                    try {
                        // 数组扩容,重置 cells 数组
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    // 扩容完成后标记碰撞为 false
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                // 重置线程 threadLocalRandomProbe 值,重新 rehash 用
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        // 初始化 cells 数组,默认大小为 2
                        Cell[] rs = new Cell[2];
                        // 角标赋值
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // 多个线程初始化,只有一个线程初始化成功,其他线程尝试更新 base 值
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

下面是具体每一个分支的详细说明:

  • cells 数组不为空,尝试更新数组中的 Cell 元素
    • 如果当前线程对应数组中的槽没有 Cell 元素,则初始化一个 Cell 元素,加锁成功后将初始化的 Cell 存在数组对应的槽中,跳出循环,槽位置 = thread.threadLocalRandomProbe & cells.length - 1,这里 & 操作相当于 %
    • 如果 wasUncontended 为 false,表示 CAS 操作失败,操作失败后会重置线程的 threadLocalRandomProbe 属性,自旋时会重新 rehash
    • CAS 操作当前数组槽对应的 Cell,累加操作的变量值,累加成功跳出循环,失败重置线程的 threadLocalRandomProbe 属性,自旋时会重新 rehash
    • cells 数组可能扩容,数组长度不能大于 JVM 的可用核心数,如果扩容,或者数组已经达到最大容量,将 collide 值置为 false
      • 这里补充说明一下,collide 为碰撞的意思,指的是多个线程经过 hash 后对应数组中的槽是否出现碰撞
      • 如果 cells 数组已经扩容到了最大限制,即使出现碰撞也不会再扩容 cells 数组了,因此将 collide 值置为 false
      • cells != as 表示数组出现了扩容,此时忽略碰撞情况,也将 collide 值置为 false
    • 如果 collide 为 false,将 collide 置为 true,意味着这此时已经出现了碰撞,出现碰撞并不会直接扩容 cells 数组,而是更新线程 threadLocalRandomProbe,自旋时重新 rehash,rehash CAS 失败后才会扩容
    • 如果出现了碰撞,且 rehash 后 CAS 更新 Cell 失败,进行加锁,加锁成功对 cells 数组扩容
  • cells 数组还没有初始化,且线程加锁成功,则初始化 cells 数组容量为 2,且将当前线程对应的 value 值封装成 Cell 元素,存储 cells 数组中
  • 可能有多个线程尝试初始化 cells 数组,但最终成功的只有一个,其他初始化失败的并不会以自旋的方式操作 cells 数组,而是尝试通过 CAS 去操作 base 值,因此在 cells 数组初始化完成之后,也是有可能是修改 base 值的

到这里 LongAdder 的原理就介绍完了,这时再来看以下几个问题?

  1. cells 数组初始化完成是不是就不会再更新 base 值了?

答:不会,可能有多个线程尝试初始化 cells 数组,最终只有一个线程成功,失败的线程还会以 CAS 的方式更新 base 值

  1. cells 数组什么时候扩容?

答:多个线程操作 cells 数组出现槽碰撞,碰撞后并不会直接扩容,而是修改线程的 threadLocalRandomProbe 值,以自旋的方式重新 rehash,如果还出现碰撞(此时 collide = true),则扩容 cells 数组

  1. cells 数组的最大容量是多少?

答:上面代码过程中有一个 else if (n >= NCPU || cells != as) 判断,这个 NCPU 表示 JVM 可用核心数,NCPU = Runtime.getRuntime().availableProcessors(); 。注意这个 JVM 可用核心数并不一定等于 CPU 核心数,比如我的电脑是 6 核,JVM 可用核心数是 12。else if (n >= NCPU || cells != as) 意味着数组的容量不能大于 JVM 的可用核心数,假设一个服务器 JVM 可用核心数为 6,由于数组每次扩容 2 倍,第一次初始化时为 2,那最大容量应该为 4。其实不是这样的,因为这个判断是在扩容前进行的,假设此时数组容量为 4,由于可用核心数为 6,条件判断通过,且存在碰撞情况,那么还是会扩容 cells 的容量为 8。因此我认为 cells 数组的最大容量为第一个大于 JVM 可用核心数的 2 的幂。

如果以上分析有错误分歧,欢迎大家在下面留言交流指正。

参考

LongAdder and LongAccumulator in Java

A Guide to False Sharing and @Contended

How do cache lines work?

JAVA 拾遗 — CPU Cache 与缓存行

Java并发工具类之LongAdder原理总结

34、实数域上高效负循环卷积算法解析 本文介绍了一种高效的实数域负循环卷积算法,通过引入离散傅里叶负循环变换(DFNT)避免传统方法的冗余,显著提升计算效率。文章详细分析了算法原理、误差传播机制,并推导出误差与方差界,提出无误差和基于3σ规则的参数设置方案。结合Cooley-Tukey与Gentleman-Sande FFT路径优化,实验验证了算法在不同精度和多项式长度下的性能优势与结果正确性,为密码学与信号处理等领域提供了高精度、高性能的卷积计算解决方案。 阅读详情

相关推荐

抖音直播行为规范考试

抖音直播行为规范考试

Q1642968641的博客 1万+

LongAdder和AtomicLong

LongAdder AmoticLong AtomicLong是作用是对长整形进行原子操作。 原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束。 在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。 构造方法 AtomicLong() 创建一个新的AtomicLong,初始值为 0 。 AtomicLong(long initialValue)

zzcheng_gogo的博客 1224

手把手教你用gmid方法设计二级运放:从理论到仿真全流程(附避坑指南)

本文详细介绍了使用gmid方法设计二级运放的全流程,从理论到仿真实践,特别针对初学者常见问题提供避坑指南。通过系统化的晶体管尺寸确定技术,帮助工程师在性能、功耗和面积之间找到最佳平衡点,适用于模拟集成电路设计中的关键模块开发。

weixin_29213525的博客 272

java统计计数_java – 使用LongAdder计算统计计数器的最大值?

我们使用AtomicLongs收集一些统计数据.一些用户看到了这些争用,并建议使用LongAdder.但是我认为无法计算最大值,因为我们目前正在使用Atomic:AtomicLong _current, _total, _max;...void add(long delta){long current = _current.addAndGet(delta);if (delta>0){_tot...

weixin_42128141的博客 461

手持式激光测距仪全套方案

激光测距全套方案,包括原理图、源代码、PCB、BOM、光学部分资料,结构、特殊元件数据手册及供应商联系方式,提供调试技术文档。全套方案已成功打板验证,实现0.05~50m测量范围,精度在+/—1.5mm,激光波长635~650nm,功率《1mW。方案预留了TTL串口、RS485接口,可通过串口助手看到测量数据,可自行DIY或者商业开发。无论是电路小白,还是技术大拿,都能通过方案资料成功做出一套激光测距仪。

Laser01的博客 2055

Leads、SDR、MQL、SQL、L2C术语概念,线索创建与清洗、线索分级模型、线索流转策略、线索的统计与分析

CRM中的“Leads”是什么?笔者在文中不仅较为全面地介绍了相关术语概念,还分析了线索的创建与清洗、线索的分级模型、线索流转策略以及线索的统计与分析。

盼盼学士 5960

你还只会用 AtomicXXX ?!恭喜你,可以下岗了!

在之前的文章中,栈长介绍了 LongAdder 的使用,性能实在太炸了,你还在用 AtomicInteger、AtomicLong 吗?如果你还不知道 LongAdder,赶紧看我之前写的那篇文章。 上次也提到了,在 JDK 8+ 中的 atomic 包下,还有另外一个兄弟类:LongAccumulator: 这个类又是什么鬼?这篇栈长继续带大家揭开它神秘的面纱! LongAccumulator LongAccumulator 可以理解为 LongAdder 的增强版,它的功能要比 LongAdder 要

Java技术栈,分享最主流的Java技术 416

数据中台之流程引擎:Activiti工作流引擎详解

一. 简介 BPM: BPM规范及BPM软件 BPMN: Activiti 7 1.工作流俩种方式 1.状态值跟踪法 1.工作流软件 2.工作流引擎对比 1.Activiti 2.JBPM5 3.flowable 4.liteflow 3.步骤 1.部署Activiti 1.流程定义 1.流程定义部署 1.启动流程实例 1.任务查询代办 1.用户办理任务 1.流程结束 二. 核心 Activiti 基础架构.png Activiti 7大接口.png RepositoryService:提供一系列管理流

GIS摆渡人 1688

LongAdder 和 AtomicLong

有幸看到一篇关于这个讲解 2个类的讲解,自己也归纳总结一下。 一、解析 看源码底层会发现实现机制不一样,当然这个也是必须的 LongAdder 点进去之后会发现,CAS 它是一个CAS的实现类。至于Cell类JVM提供的内置函数 官方说法是: 仅支持原始访问和 CAS 的 AtomicLong 的填充变体。 JVM 内在函数注意:如果提供了 CAS,则可以在此处使用仅发布形式的 CAS。 AtomicLong 而Unsafe 官方自己说明一组不安全的方法,而Unsafe...

rclack的博客 2291

Unity3D实现视频和模型融合效果

在Unity中,视频和3D模型融合通常涉及到将视频表面内嵌到3D场景中。视频不影响模型的展示, 这可以通过使用Shader和Texture来实现。大家好,我是心疼你的一切,不定时更新Unity开发技巧,觉得有用记得一键三连哦。下面就让我们进入正文吧!提示:以下是本篇文章正文内容,下面案例可供参考本次介绍的就是视频和模型融合的实现,以后有其他的需要添加的会继续添加不定时更新Unity开发技巧,觉得有用记得一键三连哦。么么哒。

心疼你的一切博客 5357

出现 Error:Unable to access jarfile xxxx\target\nacos-server.jar 解决方法

Nacos中的 2.x执行步骤如上,如果需要安装下载1.x版本,执行该步骤之后 自动更新下载的是2.x版本 对此如果需要下载安装1.x的版本,可通过如下连接(windows版本)

码农研究僧的博客 5699

简单对比AtomicLong和LongAdder

简单对比AtomicLong和LongAdder

心向阳光的天域的博客 1189

ThingsBoard-阿里云短信规则节点

由于Thingsboard平台国内只支持短信服务,那我们运维人员去及时接收邮箱肯定是没有短信来的方便的,在这基础上,需要自己从规则链入手,自定义一个支持阿里云短信服务的规则节点。

weixin_67440240的博客 2094

多线程编程:并发加/减操作优化, LongAdder原理,与AtomicLong比较

LongAdder源码解读及性能分析

wzj_whut的专栏 1513

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

java工具类解读,Java并发工具类LongAdder原理实例解析

LongAdder实现原理图高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源。LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发...

weixin_28949355的博客 246

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

LongAdder(高性能原子累加器)源码分析

LongAdder的核心思想就是通过空间换时间,将热点value分散成一个Cell数组来承接并发的CAS,从而提升性能。但是AtomicLong可以彻底被取代了吗?虽然看上去LongAdder性能远超了AtomicLong,但是也要分场景使用,如果是并发不太高的系统,使用AtomicLong可能会更好一些,而且内存需求也会小一些。同时,sum()

TheAnnoyingDog的博客 679

Sentinel底层计数器:doocs/source-code-hunter中的LongAdder应用

在分布式系统中,流量控制(Flow Control)是保障服务稳定性的核心机制。传统计数器(如`AtomicLong`)在高并发场景下存在**性能瓶颈**——所有线程竞争同一变量的更新权,导致大量CAS(Compare-And-Swap)操作失败和总线风暴。Sentinel作为阿里开源的流量治理框架,创新性地采用`LongAdder`作为底层计数器,在`doocs/source-code-hunt...

gitblog_01054的博客 1024

3 张图带你看透 LongAdder

本文内容:1、AtomicLong 解决并发的方案?2、AtomicLong 的不足是什么?3、LongAdder 又是怎么弥补这一不足的?在我的过往经历中,用到原子类的地方不多,就算用到了,AtomicInteger 也能满足业务场景,其它几个原子类从来没用过。为什么研究 AtomicLong 和 LongAdder ?一是前段时间组内一起学习并发相关知识的过程中涉及到......

JiuQianWan的博客 243

java原子操作类实现原理

java原子操作类实现原理

SO_zxn的博客 1275

JDK 8 新增的 LongAdder,得过来看一下!

前言 在介绍 AtomicInteger 时,已经说明在高并发下大量线程去竞争更新同一个原子变量时,因为只有一个线程能够更新成功,其他的线程在竞争失败后,只能一直循环,不断的进行 CAS 尝试,从而浪费了 CPU 资源。而在 JDK 8 中新增了 LongAdder 用来解决高并发下变量的原子操作。下面同样通过阅读源码来了解 LongAdder 。 公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导! 介绍 一个或多个变量共同维持初值.

程序员小航 303

并发包源码解读——AtomicLong&LongAdder&LongAccumulator

之前写了StampedLock源码简直要吐血,发现看的太细了好像也不太好,今天看点容易看懂的 由于为了跟LongAdder比较,特意选了AtomicLong,其他的还有AtomicInteger、AtomicBoolean等等 1、AtomicLong 以一个简单的例子起步 public static void main(String[] args) { final AtomicLong atomicLong = new AtomicLong(); atomicLong.ge

大造梦家的博客 364

原子操作类Atomic

2.Thread.sleep→CountDownLatch 2. 数组类型原子类 AtomicIntegerArray AtomicLongArray AtomicReferenceArray 3.引用类型原子类 AtomicReference 自旋锁SpinLockDemo AtomicStampedReference 携带版本号的引用类型原子类,可以解决ABA问题解决修改过几次状态戳原子引用ABADemo AtomicMarkableReference 原子更新带有标记位

Lifelong learning 4245

java多线程并发基础二

一、java原子类并发原理 JUC提供了一系列原子性操作类,这些类都是使用非阻塞算法CAS实现的,比使用锁实现原子性操作在性能上有很大提升 1.1 AtomicLong原理解析 public class AtomicLong extends Number implements java.io.Serializable { private static final long serialVe...

Suarge的博客 522

5、共享模式之无锁(下)

7、原子累加器 累加器性能比较 private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) { T adder = adderSupplier.get(); List<Thread> threads = new ArrayList<>(); long start = System.nanoTime();

u011569827的博客 424

bilibili尚硅谷周阳老师JUC并发编程与源码分析课程笔记第八章——原子操作类之18罗汉增强

public final int get() //获取当前的值public final int getAndSet(int newValue) //获取当前的值,并设置新的值public final int getAndIncrement() //获取当前的值,并自增public final int getAndDecrement() //获取当前的值,并自减public final int getAndAdd(int delta) //获取当前的值,并加上预期的值。

qq_46601365的博客 1175

十二、原子操作类之18罗汉增强

十二、原子操作类之18罗汉增强1、基本类型原子类1、常用API简介2、tsleep→countDownLatch2、数组类型原子类3、引用类型原子类4、对象的属性修改原子类1、使用目的2、使用要求3、AtomicIntegerFieldUpdaterDemo4、AtomicReferenceFieldUpdater5、你在哪里用了volatile6、原子操作增强类原理深度解析1、点赞计数器,看看性能2、LongAdderAPIDemo3、LongAdder高性能对比Code演示4、源码、原理分析1、架构2、

小薛博客-助力人人成为架构师 232

【尚硅谷周阳--JUC并发编程】【第九章--原子操作类之18罗汉增强】

AtomicLong的自旋会称为瓶颈 N个线程CAS操作修改线程的值,每次只有一个成功过,其他N-1失败,失败的不停的自旋知道成功,这样大量失败自旋的情况,一下子CPU就打高了; LongAdder当需要在高并发下有较好的性能表现,且对值得精度要求并不高时,可以使用。保证性能,精度代价, LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

qq_45046083的博客 637
上一篇: 深入理解 Java 泛型擦除机制
下一篇: Netty时间轮调度原理分析,再不了解你就out啦
留兰香丶
博客等级 码龄9年 391粉丝 187原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值