绝大多数场景选longadder;仅需非加法运算或非零初始值时才用longaccumulator,它是定制版而非升级版,构造和调用成本更高。

LongAccumulator 和 LongAdder 到底该选谁?
绝大多数场景下,LongAdder 更轻量、更高效;只有当你需要非加法运算(比如取最大值、乘法、自定义公式)或非零初始值时,才该用 LongAccumulator。它不是“升级版”,而是“定制版”——功能多,但构造和调用成本略高。
-
LongAdder固定做加法,初始值只能是 0;LongAccumulator的初始值由你指定(比如1或Long.MIN_VALUE) - 构造
LongAccumulator必须传入一个LongBinaryOperator,哪怕只是写(a, b) -> a + b,也比直接 newLongAdder()多一次函数对象创建 - 在纯累加且初始为 0 的场景下强行用
LongAccumulator,属于过度设计,还可能因 lambda 捕获带来轻微 GC 压力
怎么写一个安全的 max 计算器?
这是 LongAccumulator 最典型且不可替代的用途:多线程并发更新“当前最大延迟”“最高温度”“峰值 QPS”等指标。关键在于运算规则必须满足**结合律和幂等性**,否则结果不可靠。
- 正确写法:
new LongAccumulator((a, b) -> Math.max(a, b), Long.MIN_VALUE) - 错误写法:
(a, b) -> a > b ? a : b + 1—— 破坏幂等性,同一值反复 accumulate 可能持续变大 - 初始值不能设成
0(比如测响应时间),否则负延迟或未初始化数据会污染结果;Long.MIN_VALUE是更稳妥的起点 - 注意:
Math.max是无副作用纯函数,适合并发;别在里面加日志、远程调用或修改外部状态
accumulate() 方法传参容易忽略什么?
accumulate(x) 中的 x 不是“要加的数”,而是“参与二元运算的右操作数”。它的含义完全取决于你传入的 LongBinaryOperator,这点和 LongAdder.add(x) 的直觉不同。
- 如果你用的是加法:
(a,b)->a+b,那x就是增量值,和add(x)行为一致 - 如果你用的是乘法:
(a,b)->a*b,那x就是乘数(比如传2表示翻倍),不是“加 2” - 如果你用的是位或:
(a,b)->a|b,那x是掩码值,常用于状态聚合(如多线程收集错误码 bitset) - 传错语义会导致结果完全偏离预期,且很难通过单元测试覆盖所有并发路径——务必在文档或变量命名里明确
x的业务含义
为什么 get() 结果有时“看起来没更新”?
get() 返回的是当前快照值,不阻塞也不同步,它反映的是 base + 所有已分配 Cell 的瞬时和。在高并发写入过程中,部分线程可能刚完成哈希定位但还没写入对应 Cell,此时 get() 就不包含这部分值。
- 这不是 bug,是设计取舍:牺牲强一致性换取吞吐量,和
LongAdder.sum()行为一致 - 如果业务要求强一致(比如财务对账),不要依赖
get(),应改用带锁的普通long或AtomicLong(但会损失性能) - 调试时别在循环里高频打日志输出
get(),这会放大“卡顿感”;可改用定期采样或配合System.nanoTime()观察趋势 - 扩容期间(
cells数组增长)也可能导致短暂的值“丢失”,但概率低,且仅限扩容窗口期
LongBinaryOperator 在并发语义下是否真的可交换、可重复、无副作用——多数线上问题都栽在这一步。










