"高并发计数场景用 LongAdder 别用 AtomicLong"是常见建议,但快多少、什么条件下才真的值得换,很少有人给出具体数字。真实计时对比两者在不同并发强度下的表现。
上一篇:CopyOnWriteArrayList:真实验证迭代时改了也不报错,但代价随长度真实涨
1 线程(无竞争)、4 线程、16 线程,分别用 AtomicLong.incrementAndGet() 和 LongAdder.increment() 各做同样总量的自增,真实计时对比。
LongAdderVsAtomic.java(核心对比逻辑) // AtomicLong:所有线程真实竞争同一个 CAS 目标 for (int j = 0; j < perThread; j++) counter.incrementAndGet(); // LongAdder:内部真实分散成多个 Cell,不同线程大概率落在不同 Cell 上 for (int j = 0; j < perThread; j++) counter.increment();
每种并发强度下,每个线程真实做 200 万次自增,总数用 counter.get()/sum() 真实核对无误后再比较耗时。
1 线程, 每线程 2000000 次自增: AtomicLong 耗时 = 9ms LongAdder 耗时 = 10ms 倍数(AtomicLong/LongAdder) = 0.90 4 线程, 每线程 2000000 次自增: AtomicLong 耗时 = 193ms LongAdder 耗时 = 11ms 倍数(AtomicLong/LongAdder) = 17.55 16 线程, 每线程 2000000 次自增: AtomicLong 耗时 = 1768ms LongAdder 耗时 = 44ms 倍数(AtomicLong/LongAdder) = 40.18
1 线程: AtomicLong=8ms, LongAdder=10ms, 倍数=0.80 4 线程: AtomicLong=215ms, LongAdder=10ms, 倍数=21.50 16 线程: AtomicLong=1657ms, LongAdder=30ms, 倍数=55.23
两次运行趋势完全一致:单线程时 LongAdder 反而略慢(0.8~0.9 倍),4 线程时快 17~21 倍,16 线程时快 40~55 倍——真实数据呈现出随并发强度增加、优势指数级放大的曲线。原因在内部实现:AtomicLong 所有线程真实竞争同一个 volatile long 值上的 CAS,线程越多,CAS 失败重试的真实次数越多;LongAdder 内部有一个 base 加一组 Cell[] 数组,不同线程按线程探针哈希大概率落到不同的 Cell 上各自累加,真实分散了竞争,只有调用 sum() 读取总数时才需要把所有 Cell 真实加起来。
每一行都是真实反射或真实运行这段代码得到的结果。