2. 概述
本文将深入探讨java.util.concurrent
包中的两个高效并发工具:LongAdder和LongAccumulator。这两个类专为多线程环境设计,通过巧妙的策略实现了无锁(lock-free)操作,同时保证线程安全,是高并发场景下的性能利器。
✅ 核心优势:
- 无锁设计
- 线程安全
- 适用于高并发计数场景
3. LongAdder详解
当面对频繁的数值累加操作时,AtomicLong
可能成为性能瓶颈。其依赖的CAS(Compare-And-Swap)操作在激烈竞争下会导致大量CPU周期浪费。而LongAdder
通过动态分段计数技术完美解决了这个问题。
3.1 工作原理
LongAdder
的核心思想是:维护一个可动态扩展的计数器数组。当多个线程并发调用increment()
时:
- 线程被分配到数组的不同位置
- 各自更新独立的计数器
- 最终通过
sum()
合并结果
这种设计极大减少了线程争用(contention),显著提升并发性能。
3.2 使用示例
LongAdder counter = new LongAdder();
ExecutorService executorService = Executors.newFixedThreadPool(8);
int numberOfThreads = 4;
int numberOfIncrements = 100;
Runnable incrementAction = () -> IntStream
.range(0, numberOfIncrements)
.forEach(i -> counter.increment());
for (int i = 0; i < numberOfThreads; i++) {
executorService.execute(incrementAction);
}
⚠️ 注意:最终结果需通过sum()
获取:
assertEquals(counter.sum(), numberOfIncrements * numberOfThreads);
3.3 重置操作
如需清空计数器重新开始,使用sumThenReset()
:
assertEquals(counter.sumThenReset(), numberOfIncrements * numberOfThreads);
assertEquals(counter.sum(), 0); // 验证已重置
3.4 DoubleAdder
Java还提供了DoubleAdder
用于double
类型的高效累加,API与LongAdder
类似。
4. LongAccumulator详解
LongAccumulator
是更通用的累加器,支持自定义的二元操作(类似于Stream API的reduce()
操作)。它允许实现各种无锁算法,适用于需要自定义累积逻辑的场景。
4.1 核心特性
✅ 关键要求:必须提供可交换(commutative)的操作函数,即操作顺序不影响结果:
LongAccumulator accumulator = new LongAccumulator(Long::sum, 0L);
4.2 使用示例
int numberOfThreads = 4;
int numberOfIncrements = 100;
Runnable accumulateAction = () -> IntStream
.rangeClosed(0, numberOfIncrements)
.forEach(accumulator::accumulate);
for (int i = 0; i < numberOfThreads; i++) {
executorService.execute(accumulateAction);
}
📝 操作逻辑:
- 执行自定义的
LongBinaryOperator
- 检查
previousValue
是否变化 - 若变化则用新值重试
- 否则更新累加器值
4.3 结果验证
assertEquals(accumulator.get(), 20200); // 4线程 * (0+100)*101/2 = 20200
4.4 DoubleAccumulator
同样存在DoubleAccumulator
用于double
类型的自定义累加。
5. 动态分段技术
所有Adder和Accumulator实现都继承自Striped64
基类,其核心是动态分段(Dynamic Striping)技术:
5.1 工作机制
❌ 传统方式:单一内存位置 → 高争用 ✅ Striped64方式:内存位置数组 → 分散争用
图解:不同线程更新不同内存位置,通过数组(分段)分散竞争压力
5.2 伪共享问题
⚠️ 潜在陷阱:JVM可能将相邻状态分配在同一缓存行(cache line),导致伪共享(False Sharing):
- 更新一个位置导致附近位置缓存失效
- 显著降低性能
5.3 解决方案
通过@Contended
注解添加填充字节(padding),确保每个状态独占缓存行:
图解:填充字节确保状态间缓存行隔离,避免伪共享
💡 权衡:以内存消耗换取性能提升
6. 总结
LongAdder
和LongAccumulator
通过动态分段和CAS优化,提供了高性能的无锁并发解决方案:
✅ 适用场景:
- 高频计数器(LongAdder)
- 自定义累加逻辑(LongAccumulator)
- 对性能敏感的并发系统
⚠️ 注意事项:
LongAdder
的sum()
操作可能耗时LongAccumulator
需保证操作可交换- 伪共享问题需通过填充解决
完整示例代码可在GitHub项目中获取(Maven项目,可直接导入运行)。