IIWAB AtomicLong vs LongAdder - IIWAB

AtomicLong vs LongAdder

IIWAB 22天前 ⋅ 59 阅读

一、AtomicLong 的瓶颈:单点 CAS 竞争

AtomicLong 内部只有一个 volatile long value,所有线程并发自增/自减时,全部争抢同一个变量的 CAS 权限

  • 低并发:CAS 成功率高,性能好。
  • 高并发(如 1000+ 线程):
    • CAS 大量失败 → 线程自旋重试 → CPU 飙高。
    • 重试越多,冲突越剧烈,形成恶性循环,QPS 断崖式下跌。 本质:单点热点,所有线程挤一条独木桥

二、LongAdder 的核心思想:热点分散到多个 Cell

JDK1.8 引入 LongAdder,核心是分段计数、分散热点:把一个 value 拆成 base + Cell[] 数组,用空间换时间,将单点竞争分散到多个独立的 Cell 上。

1. 核心结构(父类 Striped64)

transient volatile long base;          // 基础值(低并发用)
transient volatile Cell[] cells;       // 分段数组(高并发用,2的幂次方)
transient volatile int cellsBusy;      // 扩容/初始化时的自旋锁

@sun.misc.Contended  // 伪共享防护:每个Cell独占缓存行
static final class Cell {
    volatile long value;
    boolean cas(long cmp, long val);   // 仅更新当前Cell的CAS
}
  • base:低并发时直接 CAS 更新,等价于 AtomicLong。
  • Cell[]:高并发时启用,每个 Cell 是独立的原子变量,线程只更新自己映射到的 Cell。
  • @Contended:解决伪共享(多个 Cell 挤在一个 CPU 缓存行,一个修改导致其他缓存失效),每个 Cell 独占 64 字节缓存行。

2. 工作流程(add(x) 方法)

  1. 无竞争/低并发:直接 CAS 更新 base,成功返回。
  2. 首次 CAS 失败(有竞争)
    • 初始化 cells 数组(默认长度 2)。
    • 线程用 ThreadLocalRandom.getProbe() 哈希 → 映射到某个 Cell 槽位。
  3. 高并发/Cell 冲突
    • 线程只对自己的 Cell 做 CAS,不同线程操作不同 Cell,冲突概率指数级下降。
    • 若当前 Cell 仍冲突:rehash 换槽位;冲突严重时扩容 cells(2→4→8…≤CPU核数),进一步分散压力。
  4. 取值(sum())base + 所有 Cell.value 累加,最终一致,非强实时

三、AtomicLong vs LongAdder 对比

维度AtomicLongLongAdder
竞争模型单点 CAS,所有线程抢一个 value多点 CAS,线程分散到不同 Cell
高并发性能差(自旋多、CPU高)极好(冲突少、CPU低)
低并发性能优(无额外开销)略差(需维护 cells)
读取精度强一致(实时准确)最终一致(sum() 有短暂误差)
内存占用小(仅1个 long)大(Cell 数组,随并发扩容)
适用场景读多写少、强一致、低并发写多读少、计数统计、高并发

四、为什么能解决 CAS 争抢?(关键)

  1. 热点分散:把 1 个竞争点拆成 N 个(N=cells.length),冲突概率从 100% 降到 1/N
  2. 无锁化分段:线程只操作自己的 Cell,互不干扰,无需等待其他线程。
  3. 动态扩容:并发越高,cells 越大,分散效果越好,自适应压力
  4. 伪共享防护:@Contended 让每个 Cell 独占缓存行,避免“修改一个、影响一群”的缓存颠簸。

五、代码示例:替换 AtomicLong 为 LongAdder

1. AtomicLong(高并发下性能差)

AtomicLong counter = new AtomicLong(0);
// 多线程并发自增
counter.incrementAndGet();

2. LongAdder(高并发推荐)

LongAdder counter = new LongAdder();
// 多线程并发自增(无锁、分散到不同Cell)
counter.increment();
// 最终取值(累加base+所有Cell)
long total = counter.sum();

六、适用场景与注意事项

✅ 推荐用 LongAdder

  • 高并发计数(如接口 QPS、订单数、统计指标)。
  • 写多读少,允许最终一致性(非实时精确)。
  • 并发线程数 ≥ CPU 核数(冲突明显)。

❌ 不推荐用 LongAdder

  • 读多写少、低并发(AtomicLong 更省内存、更快)。
  • 要求强一致性实时值(如金融交易金额)。
  • 需要精确的 CAS 结果(如 compareAndSet 逻辑)。

七、总结

  • AtomicLong:单点 CAS,高并发下竞争激烈、性能雪崩。
  • LongAdderbase + Cell[] 分段计数,把单点热点分散到多个 Cell,线程仅竞争自己的 Cell,冲突概率指数级下降,高并发性能提升数倍到数十倍。
  • 本质:用空间换时间,分而治之,消除单点瓶颈

全部评论: 0

    我有话说: