导读:本期聚焦于小伙伴创作的《LongAdder 如何实现超高并发下热点变量的分流优化?》,敬请观看详情。在秒杀或实时统计场景中,单一 AtomicLong 会因 CAS 竞争出现线程大量自旋。LongAdder 采用分段 Cell 数组分散写压力,各线程优先更新本地槽位,最终求和时累加 base 与所有 Cell。相比锁或原子类,它将写冲突从全局降为局部,使吞吐量随线程数近似线性提升。理解 Cell 的惰性初始化、hash 探测与扩容机制,能帮助我们在计数类需求里避开性能陡降区。

LongAdder 是 JUC 并发包中针对高并发计数场景设计的分段原子类。它通过在内部维护一个 base 变量与一组 Cell 数组,将原本集中在单个原子变量上的写竞争分散到多个独立内存单元上,从而显著降低热点变量在超高并发下的 CAS 失败率与线程自旋开销。

LongAdder 如何实现超高并发下热点变量的分流优化?

一、为什么 AtomicLong 在超高并发下会失效

AtomicLong 依靠 Unsafe 的 compareAndSwapLong 实现原子自增。在低频更新时,CAS 成功率很高,性能优于锁。但在秒杀、全局 QPS 统计等写热点场景中,成千上万个线程同时竞争同一个 value 的内存地址,只有一个线程能成功,其余线程不断重试,产生大量 CPU 空转。

我们可以用一段简单压测代码观察现象。假设有 64 个线程各累加一百万次,AtomicLong 的耗时往往是 LongAdder 的数倍。原因在于缓存一致性协议(如 MESI)中,同一缓存行的独占请求在多线程间频繁失效广播,导致总线风暴。这种竞争随线程数增加呈非线性恶化。

import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongDemo {
    private static final AtomicLong counter = new AtomicLong(0);

    public static void main(String[] args) throws InterruptedException {
        int threads = 64;
        Thread[] ts = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            ts[i] = new Thread(() -> {
                for (int j = 0; j < 1_000_000; j++) {
                    counter.incrementAndGet();
                }
            });
        }
        long start = System.currentTimeMillis();
        for (Thread t : ts) t.start();
        for (Thread t : ts) t.join();
        System.out.println("AtomicLong result=" + counter.get() + " cost=" + (System.currentTimeMillis() - start));
    }
}

二、LongAdder 的分流核心:Cell 数组设计

LongAdder 继承自 Striped64,其核心思想是分而治之。它内部有一个 volatile Cell[] cells 数组,每个 Cell 是一个填充了伪共享保护的独立计数单元。线程通过 threadLocalRandomProbe 哈希值定位到某个 Cell,优先更新该槽位,而不是全局 base。

当 cells 为 null 且 CAS base 成功时,走低成本路径;一旦 CAS base 失败,说明存在竞争,LongAdder 会懒初始化 cells 数组,并为当前线程分配 Cell。后续该线程大多在自家 Cell 上累加,写冲突被限制在极少数的扩容或重散列场景。求和时 sum() 方法将 base 与所有非 null Cell 的值相加,得到最终一致视图。

import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {
    private static final LongAdder adder = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        int threads = 64;
        Thread[] ts = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            ts[i] = new Thread(() -> {
                for (int j = 0; j < 1_000_000; j++) {
                    adder.increment();
                }
            });
        }
        long start = System.currentTimeMillis();
        for (Thread t : ts) t.start();
        for (Thread t : ts) t.join();
        System.out.println("LongAdder result=" + adder.sum() + " cost=" + (System.currentTimeMillis() - start));
    }
}

三、热点分流的底层机制剖析

3.1 哈希探测与槽位争用

线程首次进入累加逻辑时,若 cells 已存在,会用 probe 值按位与 (cells.length - 1) 计算索引。若该 Cell 为 null 或 CAS 失败,会调用 advanceProbe 扰动哈希并重试。这种线性探测让不同线程自然错开,避免所有线程挤在同一个 Cell。

如果连续多次探测都失败,说明当前槽位也是热点,LongAdder 会尝试扩容 cells 为两倍大小,并重新分配线程。扩容过程本身需要竞争一把自旋锁(cellsBusy),但发生频率极低,因此整体开销可控。

3.2 伪共享与 Cache 行填充

每个 Cell 内部用 @Contended 或手动 long p0~p6 填充,使一个 Cell 独占缓存行。否则相邻 Cell 在同一条缓存行时,线程 A 更新 Cell[0] 会令线程 B 的 Cell[1] 缓存失效,抵消分流收益。这种空间换时间的做法在高并发计数中至关重要。

// 简化版 Cell 结构示意(实际 JDK 使用 Unsafe 分配)
static final class Cell {
    volatile long value;
    // 缓存行填充,避免伪共享
    long p0, p1, p2, p3, p4, p5, p6;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }
}

四、使用时的注意事项与适用边界

LongAdder 的 sum() 不是原子快照,调用瞬间若有线程正在写,结果可能略低于真实值。因此它适合允许最终一致性的计数,如接口调用量、限流窗口计数,而不适合需要精确原子读写的余额字段。若业务要求每次读都绝对准确,仍应使用 AtomicLong 或加锁。

另外,LongAdder 在并发度很低时比 AtomicLong 多一次对象访问与分支判断,优势不明显。通常线程数超过 CPU 核心数且写操作密集时,分流收益才真正显现。实践中可结合监控,在 QPS 超过万级且出现 CAS 自旋告警时再引入。

对比维度AtomicLongLongAdder
竞争模型全局单点 CAS分段 Cell 本地更新
高并发写吞吐急剧下降近似线性扩展
读取一致性强一致最终一致
内存占用随并发略增

五、总结

LongAdder 通过分段原子类的设计,将超高并发下的热点变量写压力拆解到多个 Cell 中,以本地更新加最终汇总的方式换取吞吐量。理解其懒初始化、哈希探测、扩容与缓存行填充机制,能帮助我们在计数器选型时做出合理决策,在正确场景用对工具。

LongAdder分段原子类高并发计数修改时间:2026-08-01 09:57:13

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。