伪共享是多线程下因变量共享同一64字节缓存行引发的性能陷阱:线程修改各自独立变量时,导致缓存行频繁失效与同步,使程序执行时间锐减5倍以上;longadder通过@contended注解填充、cell数组分流及base兜底机制有效规避该问题。

伪共享是性能隐形杀手
CPU缓存以64字节缓存行为单位读写数据。一个 long 占8字节,单个缓存行能塞下8个 long 变量。当多个线程分别更新逻辑上无关的 long 字段(比如两个相邻的计数器),但它们恰好落在同一缓存行里,就会触发伪共享:一个核心修改自己的字段,导致整行缓存失效,其他核心必须重新加载该行——哪怕它们改的是不同字段。这种无意义的缓存同步开销,在高并发下会急剧拖慢吞吐量。
Cell 类强制独占缓存行
LongAdder 的 Cell 类不是普通容器,它用 @sun.misc.Contended 注解标记。JVM 在对象布局时会自动在 value 字段前后填充大量 padding 字段(如 p0–p6、q0–q6 等),确保整个 Cell 实例至少占据 64 字节,且严格对齐到缓存行边界。结果就是:每个 Cell 独占一个缓存行,彼此物理隔离。
- 即使多个 Cell 对象在堆内存中地址相邻,padding 保证它们不会挤进同一个缓存行
- 线程 A 更新 Cell[0].value,只影响其专属缓存行;线程 B 更新 Cell[1].value,完全不触发 A 所在缓存行的失效
- 避免了 AtomicLong 中所有线程争抢同一个 value 导致的“一改全抖”现象
数组结构 + 线程哈希映射 = 竞争自然分流
Cell 数组大小默认为 CPU 核数的幂次(如 8 核机器初始为 8 或 16 个槽位)。每个线程通过 getProbe() 获取自身哈希值,并与数组长度减一做按位与(as[getProbe() & (m)]),快速定位到某个 Cell 槽位。这个过程不加锁、无全局竞争:
- 低竞争时:多数线程命中不同槽位,各自 CAS 自己的 Cell.value,互不干扰
- 偶发冲突时:CAS 失败后,线程会尝试 rehash 或扩容数组,而非死等或重试原位置
- base 字段仅作兜底:初始化未完成或极低竞争时使用,不承担主要压力
sum() 是最终一致性,不是强一致读
调用 sum() 时,LongAdder 遍历 cells 数组,把所有非空 Cell.value 加上 base 值返回。这个操作不阻塞写入,也不保证瞬时精确——因为某线程可能刚 CAS 成功,其值尚未被 sum() 观察到。但对监控指标、统计计数这类场景,近似总和 + 高吞吐远比毫秒级精确更重要。
- 没有读写锁,sum() 和 add() 完全并发执行
- 结果反映的是某一时刻的快照,满足最终一致性语义
- 若需强一致读,应换用 AtomicLong;若要高性能聚合,LongAdder 是更优解











