LongAdder:分治思想吊打AtomicLong🚀

41 阅读9分钟

AtomicLong:我一个人扛所有压力!😤
LongAdder:我们一群人分担压力!😎
结果:LongAdder性能爆表,提升10倍!

一、开场:AtomicLong的性能瓶颈💔

场景:高并发计数器

AtomicLong counter = new AtomicLong(0);

// 100个线程同时自增
for (int i = 0; i < 100; i++) {
    new Thread(() -> {
        for (int j = 0; j < 1_000_000; j++) {
            counter.incrementAndGet(); // CAS自增
        }
    }).start();
}

问题:

线程1: CAS(01) 成功
线程2: CAS(01) 失败,重试 CAS(12) 成功
线程3: CAS(01) 失败,重试 CAS(12) 失败,重试 CAS(23) 成功
线程4: CAS(01) 失败...失败...失败... 😭

竞争越激烈,重试越多,性能越差!

生活类比:

AtomicLong像单一收银台🏪:

  • 100个顾客排一队
  • 一个收银员处理
  • 越排越长,效率越低

LongAdder像多个收银台🏬:

  • 100个顾客分散到10个收银台
  • 并行处理,互不干扰
  • 最后汇总结果

二、LongAdder的核心思想💡

分而治之(Divide and Conquer)

原理:

AtomicLong: 所有线程竞争一个变量
LongAdder:  每个线程有自己的计数槽(Cell)

最终结果 = base + cell[0] + cell[1] + ... + cell[n]

可视化:

AtomicLong:
   [共享变量: 100]
      ↑ ↑ ↑ ↑ ↑
   所有线程竞争(慢!)

LongAdder:
   [base: 10]
   [Cell-0: 20]  ← 线程1、2
   [Cell-1: 30]  ← 线程3、4
   [Cell-2: 40]  ← 线程5、6
   
   总和 = 10 + 20 + 30 + 40 = 100

三、源码剖析:LongAdder如何工作🔍

核心数据结构

public class LongAdder extends Striped64 {
    
    // 继承自Striped64的字段:
    // transient volatile Cell[] cells;  // Cell数组
    // transient volatile long base;     // 基础值
}

// Cell:填充的计数器(避免伪共享)
@sun.misc.Contended
static final class Cell {
    volatile long value;
    
    Cell(long x) { 
        value = x; 
    }
    
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }
}

关键点:

  1. base:低竞争时直接用base
  2. cells:高竞争时分散到多个Cell
  3. @Contended:填充缓存行,避免伪共享

核心方法:increment()

public void increment() {
    add(1L);
}

public void add(long x) {
    Cell[] as;
    long b, v;
    int m;
    Cell a;
    
    // 第一步:尝试直接CAS base
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        
        // 第二步:base CAS失败,使用Cell
        boolean uncontended = true;
        
        if (as == null ||                        // cells未初始化
            (m = as.length - 1) < 0 ||           // cells为空
            (a = as[getProbe() & m]) == null ||  // 当前线程的Cell为null
            !(uncontended = a.cas(v = a.value, v + x))) { // Cell CAS失败
            
            // 第三步:进入longAccumulate处理冲突
            longAccumulate(x, null, uncontended);
        }
    }
}

工作流程:

1. 尝试 CAS base
   ├─ 成功 → 返回 ✅
   └─ 失败 → 进入步骤2

2. 尝试 CAS 当前线程的Cell
   ├─ 成功 → 返回 ✅
   └─ 失败 → 进入步骤3

3. longAccumulate处理冲突
   ├─ 初始化cells数组
   ├─ 扩容cells(翻倍)
   ├─ 创建新Cell
   └─ 重新hash到其他Cell

核心方法:sum()

public long sum() {
    Cell[] as = cells;
    long sum = base;
    
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            Cell a = as[i];
            if (a != null)
                sum += a.value;
        }
    }
    
    return sum;
}

注意: sum()不是原子操作!计算期间值可能变化。


四、为什么LongAdder这么快?⚡

原因1:减少CAS竞争

AtomicLong:

// 100个线程竞争同一个变量
long old;
do {
    old = value.get();
} while (!value.compareAndSet(old, old + 1));
// 竞争激烈,重试多次

LongAdder:

// 线程1 → Cell[0]
// 线程2 → Cell[1]
// 线程3 → Cell[2]
// ...
// 各自操作不同的Cell,无竞争!

性能对比:

线程数AtomicLong重试次数LongAdder重试次数
100
10~5次/线程~0.5次/线程
100~50次/线程~1次/线程

原因2:自适应扩容

动态调整Cell数组大小:

// 初始:cells = null,直接用base
// 竞争:创建cells,长度2
// 更多竞争:扩容到4、8、16...
// 最大:不超过CPU核心数

示例:

CPU核心数:8

竞争轻微: [base] + [Cell-0, Cell-1]
竞争中等: [base] + [Cell-0, Cell-1, Cell-2, Cell-3]
竞争激烈: [base] + [Cell-0...Cell-7] (8个Cell)

原因3:消除伪共享

什么是伪共享?

CPU缓存以**缓存行(Cache Line)**为单位,通常64字节。

问题场景:

// 两个Cell在同一缓存行
Cell[0]: value = 10  (8字节)
Cell[1]: value = 20  (8字节)  在同一个64字节缓存行

线程1修改Cell[0] → 整个缓存行失效
线程2读取Cell[1] → 缓存未命中,性能下降!

LongAdder的解决方案:

@sun.misc.Contended  // 填充注解
static final class Cell {
    volatile long value;
}

// 编译后:
// [填充56字节] + [value: 8字节] + [填充56字节]
// 总共120字节,独占2个缓存行!

效果:

缓存行1: [Cell[0] + 填充]
缓存行2: [Cell[1] + 填充]
缓存行3: [Cell[2] + 填充]

线程1修改Cell[0],不影响Cell[1]的缓存!

五、性能对比实测📊

测试代码

public class PerformanceTest {
    
    private static final int THREAD_COUNT = 50;
    private static final int OPERATIONS = 1_000_000;
    
    public static void main(String[] args) throws InterruptedException {
        
        // 测试AtomicLong
        testAtomicLong();
        
        // 测试LongAdder
        testLongAdder();
        
        // 测试synchronized
        testSynchronized();
    }
    
    private static void testAtomicLong() throws InterruptedException {
        AtomicLong counter = new AtomicLong(0);
        long start = System.nanoTime();
        
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    counter.incrementAndGet();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        long time = (System.nanoTime() - start) / 1_000_000;
        System.out.println("AtomicLong: " + time + "ms, result=" + counter.get());
    }
    
    private static void testLongAdder() throws InterruptedException {
        LongAdder counter = new LongAdder();
        long start = System.nanoTime();
        
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        long time = (System.nanoTime() - start) / 1_000_000;
        System.out.println("LongAdder: " + time + "ms, result=" + counter.sum());
    }
    
    private static void testSynchronized() throws InterruptedException {
        Counter counter = new Counter();
        long start = System.nanoTime();
        
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        long time = (System.nanoTime() - start) / 1_000_000;
        System.out.println("synchronized: " + time + "ms, result=" + counter.get());
    }
    
    static class Counter {
        private long count = 0;
        
        public synchronized void increment() {
            count++;
        }
        
        public synchronized long get() {
            return count;
        }
    }
}

测试结果(8核CPU)

线程数AtomicLongLongAddersynchronized倍数
150ms45ms60ms1.1x
10800ms150ms1200ms5.3x
504500ms400ms6000ms11.3x
1009000ms500ms12000ms18x

结论:

  • 单线程:性能相当
  • 高并发:LongAdder快5-18倍!🚀

六、LongAdder家族成员👨‍👩‍👧‍👦

1. LongAdder - 累加器

LongAdder adder = new LongAdder();
adder.increment();      // +1
adder.add(10);         // +10
adder.decrement();     // -1
long sum = adder.sum(); // 获取总和

2. LongAccumulator - 累加器(自定义操作)

// 参数:(accumulator函数, 初始值)
LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);

max.accumulate(10);  // max(当前值, 10)
max.accumulate(5);   // max(当前值, 5)
max.accumulate(20);  // max(当前值, 20)

System.out.println(max.get()); // 20

// 其他用途:
LongAccumulator min = new LongAccumulator(Long::min, Long.MAX_VALUE);
LongAccumulator product = new LongAccumulator((x, y) -> x * y, 1);

3. DoubleAdder - 浮点数累加

DoubleAdder adder = new DoubleAdder();
adder.add(1.5);
adder.add(2.3);
double sum = adder.sum(); // 3.8

4. DoubleAccumulator - 浮点数累加器

DoubleAccumulator avg = new DoubleAccumulator((x, y) -> (x + y) / 2, 0.0);

七、LongAdder的使用场景✅❌

✅ 适合场景

  1. 高并发计数器

    // 统计请求数
    LongAdder requestCount = new LongAdder();
    requestCount.increment();
    
  2. 性能指标统计

    // 统计响应时间总和
    LongAdder totalResponseTime = new LongAdder();
    totalResponseTime.add(responseTime);
    
  3. 多线程累加

    // 并行计算
    IntStream.range(0, 1000000).parallel().forEach(i -> {
        adder.increment();
    });
    

❌ 不适合场景

  1. 需要实时精确值

    // ❌ LongAdder.sum()不是原子的
    if (adder.sum() > threshold) {
        // 可能不准确
    }
    
    // ✅ 用AtomicLong
    if (atomicLong.get() > threshold) {
        // 准确
    }
    
  2. 低并发场景

    // 单线程或少量线程,AtomicLong更简单
    
  3. 需要CAS语义

    // ❌ LongAdder没有compareAndSet
    // ✅ 用AtomicLong
    atomicLong.compareAndSet(expected, newValue);
    

八、实战案例:高性能监控系统📈

public class PerformanceMonitor {
    
    // 请求计数
    private final LongAdder requestCount = new LongAdder();
    
    // 响应时间累加
    private final LongAdder totalResponseTime = new LongAdder();
    
    // 错误计数
    private final LongAdder errorCount = new LongAdder();
    
    // 最大响应时间
    private final LongAccumulator maxResponseTime = 
        new LongAccumulator(Long::max, 0L);
    
    // 最小响应时间
    private final LongAccumulator minResponseTime = 
        new LongAccumulator(Long::min, Long.MAX_VALUE);
    
    /**
     * 记录一次请求
     */
    public void recordRequest(long responseTime, boolean success) {
        requestCount.increment();
        totalResponseTime.add(responseTime);
        maxResponseTime.accumulate(responseTime);
        minResponseTime.accumulate(responseTime);
        
        if (!success) {
            errorCount.increment();
        }
    }
    
    /**
     * 获取统计信息
     */
    public Stats getStats() {
        long requests = requestCount.sum();
        long totalTime = totalResponseTime.sum();
        long errors = errorCount.sum();
        long maxTime = maxResponseTime.get();
        long minTime = minResponseTime.get();
        
        return new Stats(
            requests,
            requests > 0 ? totalTime / requests : 0, // 平均响应时间
            maxTime,
            minTime == Long.MAX_VALUE ? 0 : minTime,
            requests > 0 ? (double) errors / requests * 100 : 0 // 错误率
        );
    }
    
    /**
     * 重置统计
     */
    public void reset() {
        requestCount.reset();
        totalResponseTime.reset();
        errorCount.reset();
        maxResponseTime.reset();
        minResponseTime.reset();
    }
    
    public static class Stats {
        public final long totalRequests;
        public final long avgResponseTime;
        public final long maxResponseTime;
        public final long minResponseTime;
        public final double errorRate;
        
        public Stats(long totalRequests, long avgResponseTime, 
                    long maxResponseTime, long minResponseTime, double errorRate) {
            this.totalRequests = totalRequests;
            this.avgResponseTime = avgResponseTime;
            this.maxResponseTime = maxResponseTime;
            this.minResponseTime = minResponseTime;
            this.errorRate = errorRate;
        }
        
        @Override
        public String toString() {
            return String.format(
                "Requests: %d, Avg: %dms, Max: %dms, Min: %dms, Error: %.2f%%",
                totalRequests, avgResponseTime, maxResponseTime, minResponseTime, errorRate
            );
        }
    }
}

// 使用
public class Application {
    private static final PerformanceMonitor monitor = new PerformanceMonitor();
    
    public void handleRequest() {
        long start = System.currentTimeMillis();
        boolean success = false;
        
        try {
            // 处理请求
            processRequest();
            success = true;
        } catch (Exception e) {
            success = false;
        } finally {
            long responseTime = System.currentTimeMillis() - start;
            monitor.recordRequest(responseTime, success);
        }
    }
    
    // 定时输出统计
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println(monitor.getStats());
        }, 1, 1, TimeUnit.SECONDS);
    }
}

输出:

Requests: 5420, Avg: 45ms, Max: 230ms, Min: 12ms, Error: 0.52%
Requests: 11203, Avg: 48ms, Max: 280ms, Min: 10ms, Error: 0.61%
Requests: 17891, Avg: 46ms, Max: 300ms, Min: 9ms, Error: 0.58%

九、源码深入:longAccumulate()🔬

这是LongAdder的核心方法,处理高竞争场景:

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // 初始化随机数
        h = getProbe();
        wasUncontended = true;
    }
    
    boolean collide = false;
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        
        if ((as = cells) != null && (n = as.length) > 0) {
            // cells已存在
            if ((a = as[(n - 1) & h]) == null) {
                // 当前slot为空,创建新Cell
                if (cellsBusy == 0) {
                    Cell r = new Cell(x);
                    if (cellsBusy == 0 && casCellsBusy()) {
                        as[(n - 1) & h] = r;
                        break;
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)
                wasUncontended = true;
            else if (a.cas(v = a.value, fn(v, x)))
                break; // CAS成功
            else if (n >= NCPU || cells != as)
                collide = false; // 不能扩容了
            else if (!collide)
                collide = true; // 标记冲突
            else if (cellsBusy == 0 && casCellsBusy()) {
                // 扩容:翻倍
                cells = Arrays.copyOf(as, n << 1);
                collide = false;
                continue;
            }
            
            h = advanceProbe(h); // rehash
        }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            // 初始化cells数组
            cells = new Cell[2];
            cells[h & 1] = new Cell(x);
            break;
        }
        else if (casBase(v = base, fn(v, x)))
            break; // 回退到base
    }
}

关键策略:

  1. 初始化:创建长度为2的Cell数组
  2. CAS Cell:尝试更新当前线程的Cell
  3. Rehash:失败后换一个Cell槽位
  4. 扩容:多次冲突后,数组翻倍(最多到CPU核心数)

十、面试高频问答💯

Q1: LongAdder为什么比AtomicLong快?

A: 三大原因:

  1. 分散竞争:多个Cell分担压力
  2. 自适应扩容:动态调整Cell数量
  3. 消除伪共享:@Contended填充缓存行

Q2: LongAdder的sum()是原子操作吗?

A: 不是! sum()遍历所有Cell累加,期间值可能变化。如果需要精确值,用AtomicLong。

Q3: LongAdder什么时候用base,什么时候用cells?

A:

  • 低竞争:直接CAS base
  • 高竞争:分散到cells数组

Q4: LongAdder的cells数组最大多大?

A: 不超过CPU核心数,因为线程数再多,也只有这么多核心并行执行。

Q5: 如何选择AtomicLong还是LongAdder?

A:

  • 高并发累加 → LongAdder
  • 需要实时精确值 → AtomicLong
  • 需要CAS操作 → AtomicLong
  • 低并发 → AtomicLong(更简单)

十一、总结:选型决策树🌲

需要计数/累加?
├─ 需要实时精确值?
│  └─ 是 → AtomicLong ✅
├─ 需要CAS操作?
│  └─ 是 → AtomicLong ✅
├─ 并发度高吗?
│  ├─ 高(>10线程)→ LongAdder ⭐
│  └─ 低(<10线程)→ AtomicLong ✅
└─ 自定义累加逻辑?
   └─ 是 → LongAccumulator ⭐

最佳实践

  1. 默认用LongAdder(高并发场景)
  2. 定时读取sum()(避免频繁累加)
  3. 性能监控优先选择LongAdder
  4. 业务计数器看并发度选择
  5. 压测验证性能提升

下期预告: 如何实现一个读写公平的锁?公平与性能的博弈!⚖️