Java LongAdder使用与应用实战

 更新时间:2025年10月23日 10:59:11   作者:IT橘子皮  
LongAdder是 Java 并发编程中为高并发计数而生的利器,本文就来详细的介绍一下Java LongAdder的使用,具有一定的参考价值,感兴趣的可以了解一下

LongAdder是 Java 并发编程中为高并发计数而生的利器。下面这张表格能帮助你快速把握其全貌,之后我们再深入细节和实战。

特性维度说明
设计目标高并发场景下的高性能计数/累加操作,优化多线程写竞争 。
核心思想分而治之,分散热点​:将单一变量的竞争压力分散到多个单元(base+ Cell[]),以空间换取时间 。
关键数据结构base(基础值) 和 Cell[](单元数组) 。
写入流程1. 无竞争或低竞争时,CAS 操作直接更新 base。 2. 竞争激烈时,线程通过哈希映射到 Cell[]中的某个单元进行更新,大幅减少冲突 。
读取流程 (sum())​返回 base与所有 Cell单元值的累加和。此操作不保证强一致性,是最终一致性的,因为在求和过程中可能有其他线程正在更新 。
主要优点高并发写入性能远超 AtomicLong,有效减少 CAS 空自旋,避免高竞争下的性能骤降 。
主要缺点更高的内存消耗;读取操作 (sum()) 非原子快照,是最终一致性的;不支持 compareAndSet等原子条件更新操作 。
典型应用场景高频统计计数器(如 API 调用次数、点击量)、监控指标收集、频率统计等“写多读少”且对读的实时精确性要求不高的场景 。

💡 深入核心原理

要理解 LongAdder的高性能,需要深入其内部机制。

  • 分散热点与动态扩容

    初始时,所有线程都尝试通过 CAS 操作更新 base变量。当并发加剧,某个线程 CAS 更新 base失败时,系统会初始化一个 Cell数组(默认大小为 2)。每个线程会根据其唯一的哈希值(探针,Probe)被映射到数组的某个槽位(Cell),然后对该槽位内的值进行更新 。随着竞争持续,如果线程在指定的 Cell上更新仍然失败,Cell数组会进行扩容(通常翻倍),直到达到与 CPU 核数相当的水平,以进一步分散竞争 。这种设计将针对单一内存地址的激烈竞争,转化为对多个内存地址的相对平和的访问。

  • 解决伪共享

    Cell类使用 @sun.misc.Contended注解进行填充,以避免伪共享​(False Sharing)。伪共享是指多个看似不相关的变量因位于同一个 CPU 缓存行中,当一个处理器更新其中一个变量时,会导致整个缓存行失效,其他处理器即使使用该行内的其他变量,也需要重新从内存加载,造成性能损失。@Contended注解确保每个 Cell对象独立占据一个缓存行,从而提升缓存效率 。

  • 核心方法流程

    add(long x)方法是其核心 :

    1. 首先检查 cells数组是否已初始化。若未初始化,则尝试直接 CAS 更新 base字段。
    2. 若 CAS 更新 base失败(表明出现竞争),则进入冲突处理逻辑。
    3. 检查 cells数组是否已初始化、当前线程映射的 Cell槽位是否存在、以及尝试 CAS 更新该 Cell的值。
    4. 如果以上任意一步失败,则进入更复杂的 longAccumulate方法。该方法会处理 cells数组的初始化、扩容,以及为线程重新计算哈希值以寻找新的空闲槽位,确保更新最终能够完成 。

    sum()方法遍历 cells数组(如果已初始化),将所有非空 Cell的值与 base相加返回。由于此操作没有加锁,在并发更新时返回的是某个时刻的近似总值,具备最终一致性而非强一致性 。

🛠️ 实战应用示例

LongAdder非常适用于以下场景:

  1. API 请求统计与监控

    可以轻松统计服务的请求量、成功/失败次数、总耗时等指标 。

    public class ApiRequestMonitor {
        private final LongAdder requestCount = new LongAdder();
        private final LongAdder totalLatency = new LongAdder();
    
        public void recordRequest(long latency) {
            requestCount.increment();
            totalLatency.add(latency);
        }
    
        public MonitoringSnapshot getSnapshot() {
            // 注意:sum() 获取的是瞬时近似值
            return new MonitoringSnapshot(requestCount.sum(), totalLatency.sum());
        }
    }
    
  2. 结合 ConcurrentHashMap 进行频率统计

    这是一种常见且高效的模式,用于统计元素出现次数 。

    ConcurrentMap<String, LongAdder> freqMap = new ConcurrentHashMap<>();
    
    public void count(String word) {
        // 如果键不存在,则原子性地放入一个新的 LongAdder
        freqMap.computeIfAbsent(word, k -> new LongAdder())
               .increment(); // 然后递增
    }
    // 获取某个词的频率
    long frequency = freqMap.getOrDefault(word, new LongAdder()).sum();
    

⚠️ 使用注意事项与最佳实践

  1. 理解一致性语义​:LongAddersum()方法是最终一致性的。如果你的业务场景要求在任何时刻读取都必须是完全精确的值(例如金融账户余额),那么 AtomicLong或锁机制更为合适 。
  2. 关注内存占用​:Cell数组和避免伪共享的填充会带来比 AtomicLong更高的内存开销。在内存受限或并发度不高的环境中,需要权衡利弊 。
  3. 避免频繁调用 sum() ​:sum()方法需要遍历 Cell数组,在数组较大时有一定开销。应避免在性能关键路径中频繁调用 。
  4. 重置操作​:reset()方法将 base和所有 Cell置零,但此操作非原子性。通常仅在确定没有并发更新时(如一个统计周期结束清零时)使用 。

🔄 选型指南:LongAdder vs. AtomicLong

场景推荐选择理由
极高并发写入,对读的实时精确性要求不高​(如统计、监控)LongAdder写吞吐量极高,通过分散竞争避免性能瓶颈 。
低并发环境,或需要频繁读取精确瞬时值​(如序列号生成、状态标志)AtomicLong读取 (get()) 是强一致性的单次 volatile 读,性能极高;接口丰富,支持 compareAndSet等复杂原子操作 。
需要复杂的累加操作​(如求最大值、最小值)LongAccumulatorLongAdderLongAccumulator的一个特例(专用于加法)。LongAccumulator允许传入自定义二元运算符,功能更灵活 。

💎 总结

LongAdder是 Java 并发工具包中“分而治之”思想的杰出代表。它通过空间换时间,巧妙地化解了高并发下的写入竞争,在统计、监控等“写多读少”的场景下表现卓越。

选择 LongAdder的关键在于明确:​你是否愿意用读取操作的强一致性和更高的内存开销,来换取极高的并发写入性能

到此这篇关于Java LongAdder使用与应用实战的文章就介绍到这了,更多相关Java LongAdder使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 微服务之Feign的介绍与使用小结

    微服务之Feign的介绍与使用小结

    Feign 是一个声明式的伪RPC的REST客户端,它用了基于接口的注解方式,很方便的客户端配置,Spring Cloud 给 Feign 添加了支持Spring MVC注解,并整合Ribbon及Eureka进行支持负载均衡,这篇文章主要介绍了微服务之Feign的介绍与使用,需要的朋友可以参考下
    2022-07-07
  • Mybatis select记录封装的实现

    Mybatis select记录封装的实现

    这篇文章主要介绍了Mybatis select记录封装的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • 详解使用MyBatis Generator自动创建代码

    详解使用MyBatis Generator自动创建代码

    这篇文章主要介绍了使用MyBatis Generator自动创建代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • MybatisPlusException:Failed to process,Error SQL异常报错的解决办法

    MybatisPlusException:Failed to process,Error SQL异常报错的解决办法

    这篇文章主要给大家介绍了关于MybatisPlusException:Failed to process,Error SQL异常报错的解决办法,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-03-03
  • IDEA自动生成注释模板的配置教程

    IDEA自动生成注释模板的配置教程

    本文介绍了如何在IntelliJIDEA中配置类和方法的注释模板,包括自动生成项目名称、包名、日期和时间等内容,以及如何定制参数和返回值的注释格式,需要的朋友可以参考下
    2025-04-04
  • JAVA文件读写例题实现过程解析

    JAVA文件读写例题实现过程解析

    这篇文章主要介绍了JAVA文件读写例题实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Spring Data Jpa的四种查询方式详解

    Spring Data Jpa的四种查询方式详解

    这篇文章主要介绍了Spring Data Jpa的四种查询方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Lombok的详细使用及优缺点总结

    Lombok的详细使用及优缺点总结

    最近在学Mybatis,接触到了Lombok的使用,所以写一篇文章记录一下,包括lombok的安装及使用优缺点,感兴趣的朋友跟随小编一起看看吧
    2021-07-07
  • Java接口和抽象类用法实例总结

    Java接口和抽象类用法实例总结

    这篇文章主要介绍了Java接口和抽象类用法,结合实例形式总结分析了Java接口与抽象类的具体定义、使用技巧与相关注意事项,需要的朋友可以参考下
    2015-12-12
  • idea2020中复制一个微服务实例的方法

    idea2020中复制一个微服务实例的方法

    这篇文章主要介绍了idea2020中复制一个微服务实例的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09

最新评论