#LongAdder 与 AtomicLong 对比
在 JDK 8 之前,AtomicLong 是 Java 中进行原子计数的标准选择。但当计数器成为性能瓶颈时,LongAdder 提供了更好的解决方案。理解两者的差异和适用场景,是编写高性能并发代码的关键。
#为什么需要 LongAdder
#AtomicLong 的问题
// AtomicLong 在高竞争下的问题
AtomicLong counter = new AtomicLong(0);
// 1000 个线程同时 increment
for (int i = 0; i < 1000; i++) {
new Thread(() -> counter.incrementAndGet()).start();
}高竞争下,AtomicLong 的 CAS 操作会大量失败,导致:
- 大量自旋重试
- CPU 空转
- 性能急剧下降
#LongAdder 的设计思想
#分段计数
flowchart LR
subgraph AtomicLong
A["单一 Cell\ncount"] --> |"所有线程 CAS| B["单点竞争"]
end
subgraph LongAdder
C["Cell[0]\ncount0"] --> |"线程 1| E["分段竞争"]
D["Cell[1]\ncount1"] --> E
C["Cell[2]\ncount2"] --> E
end#核心思想
- 分段:将单个计数器拆分为多个 Cell
- 分散:不同线程写入不同 Cell(通过 ThreadLocal 索引)
- 求和:读取时将所有 Cell 的值求和
#源码解析
#LongAdder 结构
public class LongAdder extends Striped64 {
// Cell 数组:核心数据结构
transient volatile Cell[] cells;
// base:初始计数,在竞争不激烈时使用
transient volatile long base;
// cellsBusy:CAS 锁标志
transient volatile int cellsBusy;
}#Cell 结构
// Striped64 内部类
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long initialValue) {
value = initialValue;
}
// CAS 更新
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
}#increment() 流程
public void increment() {
add(1L);
}
public void add(long x) {
Cell[] as;
long b, v;
int m;
Cell a;
// 1. 先尝试 CAS 更新 base(无竞争时)
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x))) {
// 2. 进入 longAccumulate
longAccumulate(x, null, uncontended);
}
}
}#longAccumulate
// 核心方法:Cell 数组初始化和扩容
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
// 初始化 ThreadLocal 随机值
ThreadLocalRandom.current();
h = getProbe();
wasUncontended = true;
}
boolean collide = false;
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
// 数组已初始化
if ((a = as[(n - 1) & h]) == null) {
// 添加新 Cell
} else if (!wasUncontended) {
// 重试
wasUncontended = true;
} else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) {
break;
} else if (n >= NCPU) {
// 达到 CPU 核心数,不再扩容
} else if (!collide) {
// 尝试扩容
collide = true;
}
} else if (cellsBusy == 0 && cellsBusy == casCellsBusy()) {
// 初始化数组
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
break;
}
}
}#性能对比
#低竞争场景
// 单线程或低竞争场景
AtomicLong counter1 = new AtomicLong(0);
LongAdder counter2 = new LongAdder();
// 性能相当,AtomicLong 略快(无额外开销)
counter1.increment();
counter2.increment();#高竞争场景
// JMH 压测对比(伪代码)
// @Benchmark
// public void atomicLongIncrement() {
// atomicLong.incrementAndGet();
// }
//
// @Benchmark
// public void longAdderIncrement() {
// longAdder.increment();
// }
// 结果(16 线程并发):
// AtomicLong: ~500,000 ops/ms
// LongAdder: ~2,000,000 ops/ms (4倍提升)#性能曲线
graph LR
subgraph 性能对比
A["线程数增加"] --> B["AtomicLong 性能下降"]
A --> C["LongAdder 性能稳定"]
end
B --> |"大量 CAS 重试| D["瓶颈"]
C --> |"分段计数| E["高吞吐"]#使用场景对比
#LongAdder 适用场景
// 1. 高并发计数器
LongAdder requests = new LongAdder();
requests.increment(); // 请求计数
LongAdder errors = new LongAdder();
errors.increment(); // 错误计数
// 2. 统计数据
LongAdder totalLatency = new LongAdder();
totalLatency.add(durationMs); // 延迟累加
// 3. QPS 统计
LongAdder qps = new LongAdder();
qps.increment(); // 每请求 +1#LongAdder 不适用场景
// 1. 需要立即读取精确值
LongAdder counter = new LongAdder();
counter.increment();
// 问题:sum() 不是原子操作
// 两次 sum() 可能返回不同值(如果有并发写入)
long value1 = counter.sum();
long value2 = counter.sum(); // 可能不等于 value1!
// 解决方案:使用 AtomicLong
AtomicLong atomicCounter = new AtomicLong(0);
atomicCounter.increment();
long exactValue = atomicCounter.get(); // 始终精确
// 2. 竞争不激烈的场景
// AtomicLong 足够,且开销更小
// 3. 需要算术运算以外的原子操作
// AtomicLong 有 getAndIncrement, incrementAndGet 等
// LongAdder 只有 add 和 increment#注意事项
#sum() 非原子
LongAdder adder = new LongAdder();
adder.add(1);
adder.add(2);
adder.add(3);
long sum = adder.sum(); // 6
// 警告:sum() 返回的是「快照」,不是原子操作
// 在极短时间内多次 sum() 可能得到不同结果#Cell 填充(@Contended)
// JDK 8+ 使用 @Contended 避免伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;
// ...
}
// 伪共享问题:
// CPU 缓存行 64 字节
// 相邻的 Cell 可能在同一缓存行
// 一个 Cell 修改导致另一个 Cell 的缓存失效#内存开销
// LongAdder 相比 AtomicLong 有额外内存开销
// Cell 数组默认大小 = CPU 核心数
// 每个 Cell 16 字节(value 8 + 对象头 8)
// 16 核机器:16 × 16 = 256 字节(即使只有一个 Cell 在使用)#实战建议
#选择指南
flowchart TD
A["选择计数器类型"] --> B{"竞争程度?"}
B -->|"极低(<10 线程)"| C["AtomicLong\n推荐"]
B -->|"中等(10-50 线程)"| D["AtomicLong 或 LongAdder"]
B -->|"高(>50 线程)"| E["LongAdder\n强烈推荐"]
B -->|"需要精确值"| F["AtomicLong\n必须使用"]#性能测试
// JMH 测试示例
@State(Scope.Group)
@BenchmarkMode(Mode.Throughput)
public class CounterBenchmark {
AtomicLong atomicLong = new AtomicLong();
LongAdder longAdder = new LongAdder();
@Benchmark
public void atomicLong() {
atomicLong.incrementAndGet();
}
@Benchmark
public void longAdder() {
longAdder.increment();
}
}#本章总结
核心要点:
- AtomicLong 的问题:高竞争下大量 CAS 重试,性能急剧下降
- LongAdder 的设计:分段计数 + 求和,减少 CAS 竞争
- Cell 数组:每个 Cell 独立计数,通过 ThreadLocal 分散写入
- sum() 非原子:返回快照,不是精确值
- 适用场景:高并发计数器、统计数据、QPS 统计
- 不适用场景:需要精确值、竞争不激烈、算术运算以外的操作
LongAdder 是高并发计数器场景的首选。下一节我们将讲解 CompletableFuture 异步编程。