LongAdder通过分段写入和空间换时间提升高并发累加性能:线程优先CAS base,冲突后定位独立Cell槽位更新,避免单点竞争;结合@Contended缓解伪共享,sum()读写分离不阻塞写入。

LongAdder 在高并发下提升累加性能,核心不是“更快地更新同一个变量”,而是**避免让所有线程挤在同一个内存位置上争抢**。它用空间换时间,把单点竞争分散成多点低冲突操作。
分段写入:每个线程尽量往自己的槽位写
LongAdder 内部维护一个 base 值 + 一个惰性初始化的 Cell[] 数组。线程首次更新时先尝试 CAS base;一旦发生竞争(CAS 失败),就通过线程本地哈希值定位到某个 Cell 槽位,只在那个 Cell 上做 CAS 更新。不同线程大概率落在不同 Cell 上,彼此不干扰。
- Cell 数组长度是 2 的幂次(如 2、4、8…),用位运算快速取模定位,无除法开销
- 冲突时不是死等重试,而是换一个槽位再试,大幅提升成功概率
- 极端情况下数组会扩容,但扩容后新 Cell 是懒加载的,不立即分配全部内存
降低硬件级争用:缓解伪共享与缓存行同步风暴
AtomicLong 所有线程反复修改同一个 long 字段,该字段所在的 CPU 缓存行(64 字节)会被频繁无效化和同步(MESI 协议),造成总线带宽浪费和 CPU 空转。LongAdder 的每个 Cell 都用 @Contended 注解填充字节,确保它们大概率落在不同缓存行里,从硬件层面减少干扰。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- JDK 8u202 及之后版本已默认启用 -XX:+UseContended,老版本需手动开启才生效
- 即使没开启,Cell 数组本身地址连续性较低(因懒加载+动态扩容),也比 AtomicLong 更抗伪共享
读写分离设计:写吞吐不被读拖累
sum() 只是遍历当前所有非空 Cell 并加总,不加锁、不阻塞写入线程。这意味着:写操作可以持续高速进行,而读操作按需聚合,两者互不卡顿。
- 适合监控类场景:每秒上报一次 QPS,不需要每次读都精确到纳秒级一致
- 不适用于强一致性要求的场景,比如库存扣减或财务对账
- 若需“读完立刻清零”,可用 sumThenReset(),它把当前可见 Cell 和 base 一起归零并返回原值,但仍不阻塞新写入
实际效果:线程越多,优势越明显
单线程下 LongAdder 因多了哈希定位和数组访问开销,反而略慢于 AtomicLong;但当线程数升至 16–100,尤其在百万级以上累加量时,性能差距迅速拉开:
- 100 线程各执行 100 万次 increment:LongAdder 耗时常为 AtomicLong 的 1/5~1/18
- 32 核机器上,16 线程并发吞吐可高出 3–5 倍
- 压测中若看到 AtomicLong.incrementAndGet() 出现大量 CAS 失败(Unsafe.compareAndSwapLong 返回 false),就是切换信号
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










