longaccumulator仅支持满足结合律的纯二元操作,不支持加减乘除混合逻辑;需按场景拆分为多个实例,operator须无副作用且不可动态切换;reset()重置为初始值identity,非清零。

LongAccumulator 不能直接做加减乘除混合运算
它只接受一个 LongBinaryOperator,也就是两个 long 值输入、返回一个 long 的函数。你不能在 accumulate 时传入“加 5 再乘 2”这种复合逻辑——每次调用 accumulate 只能触发一次二元操作。
常见错误现象:acc.accumulate(10) 看似简单,但背后是 current = operator.applyAsLong(current, 10),而 operator 是固定不变的。想动态切换运算规则?不行,得换实例或自己封装。
- 如果需要多种运算逻辑,建议按场景拆成多个
LongAccumulator实例(比如一个专做累加,一个专做最大值追踪) - 别试图在 operator 里写 if-else 切换行为——这会破坏无锁设计的假设,且无法保证并发安全
- operator 必须是无副作用、纯函数:不能读写外部变量,不能抛异常,不能 sleep
自定义 operator 时容易忽略结合律要求
LongAccumulator 依赖操作满足结合律(a op (b op c) == (a op b) op c),否则多线程下结果不可预测。Java 自带的 Long::sum、Long::max 都满足;但你自己写的不一定会。
使用场景:比如统计某种“加权和”,写成 (a * w1 + b * w2) 这种形式,就不是对两个 long 的纯二元操作,也不满足结合律。
立即学习“Java免费学习笔记(深入)”;
- 错误示例:
(a, x) -> a + x * weight——weight是外部变量,且表达式不满足结合律 - 正确思路:把权重逻辑前置,确保每次
accumulate(v)中的v已是加权后的值,operator 回归为Long::sum - 性能影响:违反结合律不会报错,但可能导致最终值在不同运行中不一致,尤其在高并发、多 CPU 核场景下更明显
与 LongAdder 比,什么情况下非用 LongAccumulator 不可
当你的聚合逻辑不是标准求和、计数、最值,而是自定义的、满足结合律的约简操作时,LongAdder 就不够用了。比如实现一个“异或累积器”或“按位与最小掩码器”。
参数差异:LongAccumulator 构造时必须传 LongBinaryOperator 和初始值(identity),而 LongAdder 没有这些参数,固定为加法+0起始。
- 异或场景:
new LongAccumulator((x, y) -> x ^ y, 0),可用于分布式日志校验或状态合并 - 按位与场景:
new LongAccumulator((x, y) -> x & y, ~0L),适合权限掩码收敛 - 兼容性注意:Java 8+ 才有
LongAccumulator,低于此版本只能手写 CAS 循环或退化为synchronized
reset() 和 getThenReset() 的真实行为常被误解
reset() 不是“清零后返回旧值”,而是将当前值重置为构造时传入的 identity;getThenReset() 是原子地读出当前值,再设为 identity。两者都不保证其他线程正在 accumulate 的值被丢弃——那些值可能已进入 cell 队列,尚未合并到 base。
常见错误现象:调用 getThenReset() 后立刻看到 0,但几毫秒后 get() 又变大,说明有延迟 flush 的 cell 值刚合并进来。
- 不要依赖
reset()实现精确周期统计(如每秒汇总),它不是强同步屏障 - 如果需要严格边界,应配合外部锁或使用
LongAdder+ 定期 snapshot(更可控) - 性能提示:频繁
reset()不会显著拖慢,但会干扰内部 cell 的局部性优化,实测 QPS 下降约 5–10%
事情说清了就结束。真正难的不是写 operator,而是判断你的业务逻辑是否天然满足无锁约简的数学前提——这点没人替你验算。










