LongAdder 简介

LongAdder 是 Java 8 引入的一个高效的并发计数器类,位于 java.util.concurrent.atomic 包中。它专为高并发场景下的累加操作优化,相比传统的 AtomicLong 在多线程竞争激烈时性能更优。


🧠 一、为什么需要 LongAdder?

在并发环境中,如果多个线程频繁对一个共享变量进行自增操作(如 i++),使用 synchronizedAtomicLong 都可能导致性能瓶颈:

  • 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;
            }
        }
    }
}

说明:

方法/字段含义
cellsCell[] 数组,用于保存各个线程独立的计数单元
base初始基础值,未发生竞争时直接使用 base
add()主要逻辑:先尝试更新 base,失败则尝试更新对应的 Cell
longAccumulate()初始化 Cell 数组或扩容(2 倍增长)
使用 cellsBusy 自旋锁保证线程安全
通过线程哈希码(getProbe())分散到不同 Cell
sum()返回 base + 所有 Cell 的 value 总和
reset()将所有计数器重置为 0

Cell 类(来自 Striped64)

CellStriped64 中定义的一个静态内部类,用于存储局部计数值:

@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 对比

特性AtomicLongLongAdder
实现方式单变量 CAS分段计数器
并发性能高并发下性能下降明显高并发下性能更好
内存占用较大(cell 数组)
读写一致性实时性强最终一致(sum() 可能不精确)
适用场景低并发或实时性要求高的场景高并发累加统计场景(如计数器、指标监控)

⚖️ 六、关键设计

特性说明
分散竞争通过 Cell[] 数组分散线程竞争,避免单一变量瓶颈
动态扩容初始无 Cell,竞争时初始化 → 竞争加剧时 2 倍扩容
伪共享处理@Contended 填充缓存行,确保每个 Cell 独占缓存行
最终一致性sum() 遍历合并结果,非原子快照(适合统计场景)
低读频优化写性能极高,但读(sum())需遍历数组,适合低频读取场景

⚖️ 七、适用场景

  • 实时性要求低:如 QPS 统计、点击量计数。
  • 超高并发写入:如日志记录、分布式监控。
  • 替代 AtomicLong:当 AtomicLong 的 CAS 竞争成为瓶颈时。

注意:若需原子快照(如金融场景),使用 AtomicLong;若需高精度统计且允许短暂误差,LongAdder 是最优解。


✅ 八、注意事项

  1. sum() 不是原子的:返回的是当前快照,不能保证绝对准确。
  2. 适合只做加法的场景:不支持像 getAndIncrement() 这样的返回旧值的操作。
  3. 不要频繁调用 sum():频繁调用会增加同步开销。
  4. reset() 可以清零:适用于周期性统计。

📚 九、总结

LongAdder 是 Java 提供的一种高性能并发计数器,特别适合高并发环境下做累加统计。它的设计思路借鉴了分段锁和 Striping 技术,有效降低了线程间的竞争,提高了吞吐量。

如果应用中有如下需求:

  • 统计访问量、请求数、错误数等;
  • 多线程频繁修改同一计数器;
  • 不需要立即获取最新值;

那么 LongAdder 将是一个非常合适的选择!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值