高并发计数器LongAdder 实现原理与使用场景详解

 更新时间:2025年06月12日 11:10:41   作者:fjkxyl  
这篇文章主要介绍了高并发计数器LongAdder 实现原理与使用场景详解,通过理解 LongAdder 的设计哲学和实现细节,开发者可以在高并发场景中做出更优的技术选型,在保证线程安全的前提下实现 5-10 倍的性能提升,需要的朋友可以参考下

LongAdder 原理与应用详解

一、设计背景与核心思想

1. 传统原子类的性能瓶颈

  • AtomicInteger/AtomicLong 基于 CAS 实现
  • 高并发场景缺陷
    • CAS 失败率随竞争加剧指数上升
    • CPU 空转消耗大量资源
    • 缓存一致性流量(MESI协议)导致总线风暴

2. LongAdder 设计目标

  • 降低竞争:通过数据分片分散写压力
  • 空间换时间:牺牲部分内存换取更高吞吐
  • 最终一致性:允许读取结果存在短暂误差

二、实现原理剖析

1. 核心数据结构

// 基础值(无竞争时直接操作)
transient volatile long base;
// 分片单元数组(应对高并发)
transient volatile Cell[] cells;
// 分片单元结构(避免伪共享)
@jdk.internal.vm.annotation.Contended
static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
}

2. 分段累加流程

3. 伪共享解决方案

  • 问题根源:不同线程的变量共享同一缓存行(通常 64 字节)
  • 优化方案
    • 使用 @Contended 注解自动填充
    • 每个 Cell 独占缓存行
    • 内存布局示意:
| Cell1 (64字节) | Cell2 (64字节) | ... |

三、关键操作解析

1. 累加操作(add)

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || 
        !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

执行策略

  • 优先尝试更新 base
  • 失败后定位到线程对应的 Cell
  • 多级失败后触发数组扩容

2. 取值操作(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;
}

特点

  • 非原子快照(可能包含进行中的更新)
  • 时间复杂度 O(n)(需遍历所有 Cell)

四、示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
public class SimpleLongAdderExample {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建LongAdder实例
        LongAdder counter = new LongAdder();
        // 2. 创建线程池(模拟并发请求)
        ExecutorService executor = Executors.newFixedThreadPool(10);
        // 3. 提交100个累加任务
        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                // 每个任务累加1000次
                for (int j = 0; j < 1000; j++) {
                    counter.increment(); // 等同于add(1)
                }
            });
        }
        // 4. 关闭线程池并等待任务完成
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        // 5. 输出最终结果
        System.out.println("最终计数: " + counter.sum()); // 应输出100000
    }
}

五、性能对比数据

测试环境:

  • CPU:8 核 Intel i9-9900K
  • 内存:32GB DDR4
  • JVM:OpenJDK 17
  • 测试用例:32 线程执行 1 亿次累加
实现方案耗时 (ms)吞吐量 (ops/ms)内存占用
synchronized4,52022,123
AtomicLong1,28078,125
LongAdder235425,531
ThreadLocal 优化182549,450

六、应用场景指南

1. 推荐使用场景

场景类型典型用例优势说明
高频计数器网站 PV/UV 统计分散写竞争
监控指标采集QPS/TPS 统计允许最终一致性
分布式限流令牌桶算法实现避免 CAS 失败风暴
大数据聚合实时计算中间结果支持快速并行累加

2. 不适用场景

场景类型典型用例问题分析
精确原子操作库存扣减sum() 非原子快照
读多写少配置项更新AtomicLong 更高效
内存敏感场景海量独立计数器Cell 数组内存开销大

七、实现原理总结

设计要点实现方案解决的问题
竞争分散分片 Cell 数组降低 CAS 失败率
伪共享预防@Contended 注解提升缓存利用率
动态扩容按需创建 Cell平衡性能与内存
延迟初始化初始使用 base 变量减少内存开销
最终一致性sum() 合并所有 Cell保证最终结果正确性

通过理解 LongAdder 的设计哲学和实现细节,开发者可以在高并发场景中做出更优的技术选型,在保证线程安全的前提下实现 5-10 倍的性能提升。关键是要根据实际业务场景的读写比例、一致性要求和资源限制进行合理选择。

到此这篇关于高并发计数器LongAdder 实现原理与使用场景详解的文章就介绍到这了,更多相关LongAdder原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于Controller层和Service层的类报错问题及解决方案

    关于Controller层和Service层的类报错问题及解决方案

    这篇文章主要介绍了关于Controller层和Service层的类报错问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Springboot项目启动时端口被占用的问题及解决

    Springboot项目启动时端口被占用的问题及解决

    这篇文章主要介绍了Springboot项目启动时端口被占用的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • SpringBoot项目启动慢的5个优化技巧

    SpringBoot项目启动慢的5个优化技巧

    SpringBoot以其约定优于配置的理念和快速开发的能力,已成为Java生态中最受欢迎的框架之一,然而,随着项目规模的扩大,许多开发者会发现一个令人头疼的问题:应用启动时间越来越长,本文将深入分析SpringBoot启动慢的根本原因,并分享5个经过实战验证的优化技巧
    2026-03-03
  • Java实现的对称加密算法AES定义与用法详解

    Java实现的对称加密算法AES定义与用法详解

    这篇文章主要介绍了Java实现的对称加密算法AES,结合实例形式分析了对称加密算法AES的定义、特点、用法及使用场景,需要的朋友可以参考下
    2018-04-04
  • JAVA强密码校验算法示例代码

    JAVA强密码校验算法示例代码

    最近系统需要做用户密码升级,增加强密码校验,密码长度,复杂度等等,这篇文章主要介绍了JAVA强密码校验算法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-07-07
  • Java多线程之等待队列DelayQueue详解

    Java多线程之等待队列DelayQueue详解

    这篇文章主要介绍了Java多线程之等待队列DelayQueue详解,    DelayQueue被称作"等待队列"或"JDK延迟队列",存放着实现了Delayed接口的对象,对象需要设置到期时间,当且仅当对象到期,才能够从队列中被取走(并非一定被取走),需要的朋友可以参考下
    2023-12-12
  • maven手动上传jar包示例及图文步骤过程

    maven手动上传jar包示例及图文步骤过程

    这篇文章主要为大家介绍了maven手动上传jar包示例及图文步骤过程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • java字节码框架ASM的深入学习

    java字节码框架ASM的深入学习

    这篇文章主要给大家介绍了java中字节码框架ASM的相关资料,文中介绍的非常详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友们下面来一起学习学习吧。
    2017-01-01
  • Java使用OTP动态口令(每分钟变一次)进行登录认证

    Java使用OTP动态口令(每分钟变一次)进行登录认证

    这篇文章主要介绍了Java使用OTP动态口令(每分钟变一次)进行登录认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • java对xml节点属性的增删改查实现方法

    java对xml节点属性的增删改查实现方法

    下面小编就为大家带来一篇java对xml节点属性的增删改查实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10

最新评论