2. 概述

本文将深入探讨java.util.concurrent包中的两个高效并发工具:LongAdderLongAccumulator。这两个类专为多线程环境设计,通过巧妙的策略实现了无锁(lock-free)操作,同时保证线程安全,是高并发场景下的性能利器。

✅ 核心优势:

  • 无锁设计
  • 线程安全
  • 适用于高并发计数场景

3. LongAdder详解

当面对频繁的数值累加操作时,AtomicLong可能成为性能瓶颈。其依赖的CAS(Compare-And-Swap)操作在激烈竞争下会导致大量CPU周期浪费。而LongAdder通过动态分段计数技术完美解决了这个问题。

3.1 工作原理

LongAdder的核心思想是:维护一个可动态扩展的计数器数组。当多个线程并发调用increment()时:

  1. 线程被分配到数组的不同位置
  2. 各自更新独立的计数器
  3. 最终通过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);
}

📝 操作逻辑:

  1. 执行自定义的LongBinaryOperator
  2. 检查previousValue是否变化
  3. 若变化则用新值重试
  4. 否则更新累加器值

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. 总结

LongAdderLongAccumulator通过动态分段和CAS优化,提供了高性能的无锁并发解决方案:

✅ 适用场景:

  • 高频计数器(LongAdder)
  • 自定义累加逻辑(LongAccumulator)
  • 对性能敏感的并发系统

⚠️ 注意事项:

  • LongAddersum()操作可能耗时
  • LongAccumulator需保证操作可交换
  • 伪共享问题需通过填充解决

完整示例代码可在GitHub项目中获取(Maven项目,可直接导入运行)。


原始标题:LongAdder and LongAccumulator in Java