LongAdder 和 AtomicLong 的区别?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官抛出这个问题,通常意在考察以下几个层面:

  1. 对高并发计数器/累加器实现方案的了解: 面试官想知道你是否了解在 Java 中,除了经典的 AtomicLong,还有性能更优的替代方案 LongAdder
  2. 对性能瓶颈和优化思路的理解: 这不仅仅是记忆区别,更是考察你是否能理解 AtomicLong 在高并发写入场景下的性能瓶颈(即大量线程对同一热点变量的 CAS 竞争),以及 LongAdder 是如何通过 “空间换时间”“分散竞争” 的思路来解决这个问题的。
  3. 对底层并发机制的掌握: 深入考察你是否理解 AtomicLong 基于 UnsafeCAS(Compare-And-Swap) 操作,以及 LongAdder 所借鉴的 分段思想(Striping) 和其对 伪共享(False Sharing) 的优化处理。
  4. 场景化选型能力: 面试官希望你能够根据具体的业务场景(是频繁写入还是频繁读取,是否需要强一致的精确值)来做出合理的技术选型。

核心答案

AtomicLongLongAdder 都是 Java 并发包(java.util.concurrent.atomic)下用于高并发环境下进行原子性操作的类。它们的核心区别在于 高并发写入场景下的性能读取结果的特性

简单来说:

  • AtomicLong 内部使用一个 volatile long 变量,通过 CAS(自旋) 保证原子更新。所有线程竞争同一个变量,在竞争激烈时,大量线程会自旋重试,导致 CPU 开销激增,性能下降。
  • LongAdder 则采用了 “分段累加” 的思想。它内部维护了一个 Cell[] 数组(每个 Cell 是一个独立的原子计数器)和一个 base 变量。写操作时,通过哈希算法将线程映射到不同的 Cell 上进行累加,从而将竞争分散,大大减少了 CAS 冲突。在需要获取最终结果时,再将所有 Cell 的值和 base 累加。

因此,在并发写入远多于读取的场景下(例如统计点击数、接口调用次数),LongAdder 的性能显著高于 AtomicLong。但代价是,LongAdder 在读取值时(调用 sum())可能需要合并数据,开销稍大,且它提供的是 最终一致性 的估值,在并发累加过程中读取的值可能不精确。而 AtomicLong 的每次读取都是 强一致性 的精确值。

深度解析

原理/机制

  • AtomicLong: 其核心是 private volatile long value;。进行 incrementAndGet() 等操作时,在一个死循环中不断尝试用 Unsafe.compareAndSwapLong 方法将 value 从旧值更新为新值,直到成功。这就是 CAS 自旋。所有线程都盯着这一个 value 变量,竞争是 集中式 的。
  • LongAdder
    1. 分散竞争: 它继承自 Striped64。内部有 transient volatile Cell[] cellstransient volatile long base。当没有竞争时,操作直接作用在 base 上。一旦发生竞争(某个线程 CAS 更新 base 失败),它会初始化或扩展 cells 数组,并尝试将当前线程通过哈希值映射到数组中的一个 Cell 槽位上,后续的累加操作主要在自己的 Cell 上进行。这样,写热点就被分散到了多个 Cell 中。
    2. 避免伪共享Cell 类使用 @sun.misc.Contended 注解进行缓存行填充。这确保了每个 Cell 对象会独占一个 CPU 缓存行,防止多个 Cell 变量被加载到同一缓存行,从而避免因一个 Cell 更新导致其他 Cell 缓存失效的 “伪共享” 问题,这是实现高性能的关键细节之一。
    3. 最终一致性sum() 方法只是遍历 cells 数组,将各个 Cell 的值与 base 相加。这个求和过程没有加锁,也没有阻止并发的写操作,所以在并发累加过程中调用 sum() 得到的是一个某一时刻的近似快照,并非精确值。如果需要精确值,可以结合业务考虑在低竞争时段读取,或使用 AtomicLong

代码示例

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class CounterComparison {

    public static void main(String[] args) throws InterruptedException {
        // 场景:模拟100个线程,每个线程累加10000次
        final AtomicLong atomicCounter = new AtomicLong(0);
        final LongAdder adderCounter = new LongAdder();

        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                atomicCounter.incrementAndGet(); // AtomicLong 累加
                adderCounter.increment();        // LongAdder 累加
            }
        };

        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }
        for (Thread t : threads) {
            t.join();
        }

        System.out.println("AtomicLong 最终结果: " + atomicCounter.get()); // 强一致,精确
        System.out.println("LongAdder 最终结果: " + adderCounter.sum());    // 最终一致,此时线程已结束,所以精确
    }
}

对比分析与最佳实践

特性AtomicLongLongAdder
核心原理基于 CAS 自旋,单一变量基于 分段累加 (Cell[]),分散竞争
写入性能高竞争下差(大量自旋)高竞争下极优(竞争被分散)
读取性能(直接读 volatile 变量)较低(需要遍历 Cell 数组求和)
数据一致性强一致性,每次读取都是最新精确值最终一致性sum() 是近似快照,非精确值
内存占用较高(维护 Cell 数组)

最佳实践与选型指南

  1. 写多读少,且对读取实时性要求不高: 例如收集统计指标(QPS、错误次数)、计数器等,首选 LongAdder。它的高性能优势在此类场景下发挥得淋漓尽致。
  2. 读多写少,或需要频繁依赖当前值做决策: 例如生成序列号、需要基于当前值进行条件判断的场合,应使用 AtomicLong。因为它能提供精确、实时的最新值。
  3. 精确计数的场景: 如库存扣减(需要精确知道还剩多少),虽然 LongAdder 最终结果正确,但过程中读取的值不精确,可能影响业务判断,此时通常也更适合 AtomicLong 或使用

常见误区

  • 认为 LongAdder 在所有场景下都比 AtomicLong: 错。LongAdder 的优势仅在高并发写入场景。在低并发或读多写少的场景,AtomicLong 简单可靠,性能可能更好。
  • 忽略 LongAdder.sum() 的非原子性: 在并发环境下,sum() 返回的值可能已经“过时”,不能用于需要强一致性的场景(如作为检查条件)。
  • 低估 LongAdder 的内存开销Cell 数组和缓存行填充会带来额外的内存消耗,在内存敏感的环境中需纳入考量。

总结

LongAdder 是 JDK 8 引入的、专门针对高并发写入场景优化的计数器,它通过分段累加避免伪共享的巧妙设计,以牺牲读取性能和强一致性为代价,换取了卓越的写入吞吐量,是 AtomicLong 在高并发写竞争下的优秀替代方案。选型时,务必根据 “读写比例”“数据一致性要求” 来决策。