Java并发工具:LongAdder
LongAdder 是 Java 中
java.util.concurrent.atomic 包下的一个类,从 Java 8 开始引入。它是一个可伸缩的并发累加器,适用于高并发场景下对长整型(long)数值进行高效的递增、递减或加法操作。其核心设计通过分散热点竞争提升性能。
基本概念
LongAdder 的设计目的是为了在高并发写入的情况下提供比 AtomicLong 更好的性能。
- AtomicLong 在并发写入时容易因为线程竞争而导致大量 CAS(Compare and Swap)失败,从而降低效率。
- LongAdder 使用了分段锁的思想,内部维护多个计数单元(Cell),每个线程根据其线程局部变量访问不同的单元,减少竞争。
- 最终结果通过 sum() 方法汇总所有单元的值。
核心结构
继承关系:LongAdder继承自Striped64类,内部维护一个Cell数组和base基础值。
真实值计算:实际值 = base + 所有Cell元素的value值之和。
避免伪共享:Cell类使用@sun.misc.Contended注解填充缓存行,减少伪共享问题。
核心机制
(1) 初始化与基础更新
初始无竞争:当线程未发生竞争时,直接通过CAS操作更新base值。
竞争触发初始化:若CAS更新base失败,则初始化Cell数组(默认大小为2)。
(2) 线程访问策略
哈希映射:通过
ThreadLocalRandom.getProbe()生成线程的哈希值,与Cell数组长度取模确定访问的Cell元素。
冲突处理:若目标Cell槽位已被占用,尝试其他槽位或触发扩容。
(3) 扩容机制
扩容条件:当Cell数组的某个槽位竞争激烈(CAS失败次数多),且数组未达到CPU核心数时,触发扩容。
扩容方式:数组大小翻倍(类似ConcurrentHashMap),直到达到硬件支持的最大并行度。
(4) 原子性保证
Cell操作:每个Cell内部通过CAS更新value值。
全局锁控制:初始化、扩容等操作使用cellsBusy自旋锁(CAS控制)保证线程安全。
适用场景
- 高频写、低频读:如统计计数场景(如QPS统计),此时读取操作需遍历所有Cell求和,性能略低。
- 替代AtomicLong:在高并发写场景下,LongAdder性能显著优于AtomicLong,但会占用更多内存。
- 不适用场景:需要强一致性的实时读取场景(如余额计算)。
示例代码
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();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(counter::increment);
}
executor.shutdown();
while (!executor.isTerminated()) {
// 等待所有任务完成
}
System.out.println("Counter value: " + counter.sum()); // 输出:1000
}
}
LongAdder vs AtomicLong
特性 | LongAdder | AtomicLong |
写多读少 | 高效 | 效率低(CAS 失败多) |
读频繁 | sum() 可能不一致 | 实时一致性 |
是否最终一致 | 是 | 是 |
内存占用 | 稍大(维护 Cell 数组) | 较小 |
适用场景 | 高并发计数器(如统计请求量) | 单线程或低并发,需要强一致性 |
注意事项
- sum() 返回的是调用时刻的一个近似值,在并发修改时可能不是实时准确的,但在没有写入完成后是稳定的。
- 如果你要求每次读取都必须是最新的值(强一致性),那么应使用 AtomicLong。
- LongAdder 不支持构造函数设置初始值,初始值为 0。
- 有对应的 DoubleAdder 类用于浮点类型。