高并发计数器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原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis-plus动态数据源读写分离方式

    mybatis-plus动态数据源读写分离方式

    在分布式项目开发中,动态数据源的配置与使用至关重要,通过创建DynamicDatasourceService,实现数据源的动态添加与调用,有效管理主从库操作,减轻数据库压力,此外,通过配置类与@DS注解,实现了灵活的分库查询功能,为高效处理数据提供了强有力的支持
    2024-10-10
  • mybatis update set 多个字段实例

    mybatis update set 多个字段实例

    这篇文章主要介绍了mybatis update set 多个字段实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • Java多线程的常用创建方式总结

    Java多线程的常用创建方式总结

    今天给大家带来的是关于Java多线程的相关知识,文章围绕着Java多线程的常用创建方式展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • dubbo将异常转换成RuntimeException的原因分析 ExceptionFilter

    dubbo将异常转换成RuntimeException的原因分析 ExceptionFilter

    这篇文章主要介绍了dubbo将异常转换成RuntimeException的原因分析 ExceptionFilter问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • 浅谈Java常见的排序算法

    浅谈Java常见的排序算法

    今天给大家带来的是关于Java的相关知识,文章围绕着Java常见的排序算法展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • 详解Servlet3.0新特性(从注解配置到websocket编程)

    详解Servlet3.0新特性(从注解配置到websocket编程)

    Servlet3.0的出现是servlet史上最大的变革,其中的许多新特性大大的简化了web应用的开发,为广大劳苦的程序员减轻了压力,提高了web开发的效率。
    2017-04-04
  • SpringBoot读取配置文件的6种实现方式

    SpringBoot读取配置文件的6种实现方式

    这篇文章主要介绍了SpringBoot读取配置文件的6种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • idea更换jdk版本的完整步骤(附详细图文)

    idea更换jdk版本的完整步骤(附详细图文)

    idea很多地方都设置了jdk版本,不同模块的jdk版本也可能不一样,这篇文章主要介绍了idea更换jdk版本的完整步骤,通过文中介绍的步骤可以成功更换jdk版本,需要的朋友可以参考下
    2025-04-04
  • maven打包成第三方jar包且把pom依赖包打入进来的方法

    maven打包成第三方jar包且把pom依赖包打入进来的方法

    这篇文章主要介绍了maven打包成第三方jar包且把pom依赖包打入进来的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • Java按照List内存储的对象的某个字段进行排序的实例

    Java按照List内存储的对象的某个字段进行排序的实例

    下面小编就为大家带来一篇Java按照List内存储的对象的某个字段进行排序的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12

最新评论