LongAdder
是 Java 8 引入的一个高效的并发计数器类,位于 java.util.concurrent.atomic
包中。它专为高并发场景下的累加操作优化,相比传统的 AtomicLong
在多线程竞争激烈时性能更优。
🧠 一、为什么需要 LongAdder?
在并发环境中,如果多个线程频繁对一个共享变量进行自增操作(如 i++
),使用 synchronized
或 AtomicLong
都可能导致性能瓶颈:
AtomicLong
使用 CAS(Compare And Swap)来实现原子性,在高并发下可能因大量线程争用同一个变量而导致 CAS 失败率升高,从而降低性能。LongAdder
核心思想是 分散热点:通过将单个值的更新压力分散到多个Cell
单元中,显著减少线程竞争,从而提升吞吐量。适合写多读少的场景(如统计计数)。
📚 二、简介
核心思想:Striped64 + 分段缓存
LongAdder
的底层基于 Striped64
类,其核心设计思想是:
- 内部维护一个
Cell[]
数组,每个 Cell 相当于一个“局部计数器”。 - 线程根据当前线程的 hash 值选择一个 Cell 来更新值。
- 如果某个 Cell 更新失败(CAS 失败),则尝试重新 hash 或扩容数组。
- 最终结果通过
sum()
方法汇总所有 Cell 的值和 base 值。
这样可以大大减少线程之间的竞争,提升并发性能。
🧾 三、源码
关键字段(Striped64)
transient volatile Cell[] cells; // 分散更新的 Cell 数组
transient volatile long base; // 基础值(无竞争时使用)
transient volatile int cellsBusy; // 自旋锁(用于扩容/初始化)
LongAdder
public class LongAdder extends Striped64 implements LongAdderMBean {
// 构造函数
public LongAdder() {}
// 添加一个增量
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
// 1. 无竞争时直接更新 base
if ((as = cells) != null || !casBase(b = base, b + x)) {
// 更新 base 失败(出现竞争),进入分支处理
boolean uncontended = true;
// 2. 检查线程本地 cell 是否存在并尝试更新
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
// 3. Cell 更新失败,调用 longAccumulate 处理扩容/初始化
longAccumulate(x, null, uncontended);
}
}
// 自增方法
public void increment() {
add(1L);
}
// 自减方法
public void decrement() {
add(-1L);
}
// 获取总和(非精确值)
public long sum() {
Cell[] as = cells;
long sum = base;
if (as != null) {
for (Cell a : as) {
if (a != null)
sum += a.value;// 合并所有 Cell 的值
}
}
return sum;
}
// 重置计数器
public void reset() {
Cell[] as = cells;
base = 0L;
if (as != null) {
for (Cell a : as) {
if (a != null)
a.value = 0L;
}
}
}
}
说明:
方法/字段 | 含义 |
---|---|
cells | Cell[] 数组,用于保存各个线程独立的计数单元 |
base | 初始基础值,未发生竞争时直接使用 base |
add() | 主要逻辑:先尝试更新 base,失败则尝试更新对应的 Cell |
longAccumulate() | 初始化 Cell 数组或扩容(2 倍增长)使用 cellsBusy 自旋锁保证线程安全 通过线程哈希码( getProbe() )分散到不同 Cell |
sum() | 返回 base + 所有 Cell 的 value 总和 |
reset() | 将所有计数器重置为 0 |
Cell 类(来自 Striped64)
Cell
是 Striped64
中定义的一个静态内部类,用于存储局部计数值:
@sun.misc.Contended // 缓存行填充
static final class Cell {
volatile long value;
Cell(long initialValue) {
value = initialValue;
}
final boolean cas(long cmp, long val) {
return VALUE.compareAndSet(this, cmp, val);
}
// 使用 VarHandle 替代 AtomicLongFieldUpdater
private static final jdk.internal.misc.Unsafe UNSAFE = ...;
private static final long VALUE = ...;
}
@Contended
注解:防止多个Cell
对象在同一个 CPU 缓存行中,避免伪共享。
🧪 四、示例
多线程计数器
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderExample {
public static void main(String[] args) throws InterruptedException {
LongAdder counter = new LongAdder();
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment(); // 等价于 add(1)
}
latch.countDown();
});
}
latch.await();
executor.shutdown();
System.out.println("Final count: " + counter.sum());
}
}
统计请求次数(Web 场景模拟)
import java.util.concurrent.atomic.LongAdder;
public class RequestCounter {
private static final LongAdder requestCounter = new LongAdder();
public static void handleRequest() {
requestCounter.increment();
// 模拟业务处理
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100; j++) {
handleRequest();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("Total requests: " + requestCounter.sum());
}
}
⚖️ 五、LongAdder vs AtomicLong 对比
特性 | AtomicLong | LongAdder |
---|---|---|
实现方式 | 单变量 CAS | 分段计数器 |
并发性能 | 高并发下性能下降明显 | 高并发下性能更好 |
内存占用 | 小 | 较大(cell 数组) |
读写一致性 | 实时性强 | 最终一致(sum() 可能不精确) |
适用场景 | 低并发或实时性要求高的场景 | 高并发累加统计场景(如计数器、指标监控) |
⚖️ 六、关键设计
特性 | 说明 |
---|---|
分散竞争 | 通过 Cell[] 数组分散线程竞争,避免单一变量瓶颈 |
动态扩容 | 初始无 Cell ,竞争时初始化 → 竞争加剧时 2 倍扩容 |
伪共享处理 | @Contended 填充缓存行,确保每个 Cell 独占缓存行 |
最终一致性 | sum() 遍历合并结果,非原子快照(适合统计场景) |
低读频优化 | 写性能极高,但读(sum() )需遍历数组,适合低频读取场景 |
⚖️ 七、适用场景
- 实时性要求低:如 QPS 统计、点击量计数。
- 超高并发写入:如日志记录、分布式监控。
- 替代
AtomicLong
:当AtomicLong
的 CAS 竞争成为瓶颈时。
注意:若需原子快照(如金融场景),使用
AtomicLong
;若需高精度统计且允许短暂误差,LongAdder
是最优解。
✅ 八、注意事项
- sum() 不是原子的:返回的是当前快照,不能保证绝对准确。
- 适合只做加法的场景:不支持像
getAndIncrement()
这样的返回旧值的操作。 - 不要频繁调用 sum():频繁调用会增加同步开销。
- reset() 可以清零:适用于周期性统计。
📚 九、总结
LongAdder
是 Java 提供的一种高性能并发计数器,特别适合高并发环境下做累加统计。它的设计思路借鉴了分段锁和 Striping 技术,有效降低了线程间的竞争,提高了吞吐量。
如果应用中有如下需求:
- 统计访问量、请求数、错误数等;
- 多线程频繁修改同一计数器;
- 不需要立即获取最新值;
那么 LongAdder
将是一个非常合适的选择!