doubleadder在高并发写多读少场景下比atomiclong更优,因其分段写入降低cas冲突,吞吐量提升3–10倍,但仅适用于容忍精度误差的统计类浮点累加,不支持强一致性读与减法操作。

DoubleAdder在高并发写多读少场景下确实比AtomicLong(或AtomicDouble)更优,但这个“更优”有明确前提和适用边界——它不是通用替代品,而是为特定问题设计的专用工具。
核心优势:分段写入,避开CAS热点
AtomicLong所有线程争抢同一个内存地址,高并发时大量失败重试、CPU空转;DoubleAdder内部维护一个惰性初始化的Cell数组,线程通过哈希定位到自己的槽位写入,冲突概率大幅降低。实测在500+线程争抢场景下,吞吐量通常是AtomicDouble的3–10倍。
- 写操作(add())几乎无竞争,不自旋,延迟低
- 底层用
long模拟double位表示(Double.doubleToRawLongBits),规避了直接对double字段做CAS的JVM限制 - @sun.misc.Contended注解做缓存行填充,防止Cell数组元素间伪共享
关键约束:只适合“计数型”浮点累加
DoubleAdder不是为精确计算设计的。它适用于统计类指标,比如平均响应时间(单位毫秒,带小数)、成功率(99.98%)、采样均值等,但必须接受以下事实:
- sum()不是原子快照:执行过程中其他线程仍在写,结果是近似值,非某一时刻精确和
- 存在浮点精度累积误差:如连续add(0.1)十次,sum()可能返回0.9999999999999999而非1.0
- 不支持减法、compareAndSet、getAndIncrement等操作,API极窄,仅提供add()、sum()、sumThenReset()
比AtomicLong更优的变量选择策略
优先考虑是否真需要double类型:
- 响应时间 → 存为纳秒级
long,用LongAdder累加,最后除以1_000_000.0 - 成功率/百分比 → 按万分之一单位存储(99.98% → 9998),仍是
long,零误差 - 必须用double且业务容忍误差(如监控告警阈值宽松)→ 才启用
DoubleAdder - 若需强一致性读(如财务对账)、频繁判断
sum() == x、或支持减法 → 这两类都不适用,应回归数据库或分布式锁方案
使用时易忽略的细节
性能优势不会自动生效,需配合合理用法:
- 避免高频调用
sum()(如每100ms一次),它要遍历整个Cell数组,开销随并发峰值增长 - 周期性上报推荐用
sumThenReset(),但注意:清零与新写入存在微小窗口,少量计数可能丢失(概率低,非零) - 长生命周期服务中,突发流量后Cell数组可能残留大量空Cell,占用堆内存,GC压力上升
- key维度监控建议搭配
ConcurrentHashMap<string doubleadder></string>,但字符串拼接要复用ThreadLocal<stringbuilder></stringbuilder>降GC










