文章目录

Java LongAdder在高并发计数场景下的分段锁优化

发布于 2026-04-23 04:25:28 · 浏览 7 次 · 评论 0 条

Java LongAdder在高并发计数场景下的分段锁优化

在并发编程中,统计计数(如点击量、请求数)是极其常见的需求。JDK 8 之前,我们通常使用 AtomicLongsynchronized 来实现线程安全计数。然而,在极高的并发场景下,这两种方案都会遇到性能瓶颈。LongAdder 是 JDK 8 引入的高性能计数器,它通过“分段锁”机制解决了这一问题。


核心原理:分段锁与空间换时间

AtomicLong 依赖 CAS(Compare-And-Swap)自旋。当并发量巨大时,大量线程会同时修改同一个变量,导致 CAS 反复失败,CPU 进行大量无效的自旋空转,严重浪费计算资源。

LongAdder 的核心思想是分散热点。它不再让所有线程争抢同一个变量,而是内部维护了一个 Cell 数组(类似于并发分段)。当线程需要计数时,它会通过哈希算法映射到其中一个 Cell 上进行累加。这就像一个银行大厅,原本只有一个柜台(AtomicLong),大家排长队;现在开了多个备用窗口(Cell 数组),客户分流办理,效率自然大幅提升。

最终的结果通过将所有 Cell 的值以及基础值 base 相加得到。

$$ Sum = Base + \sum_{i=0}^{n} Cell_i $$

以下流程展示了 LongAdder 处理并发请求的内部逻辑:

graph TD A["Start: Thread Requests Increment"] --> B{"Cells Array\nInitialized?"} B -- No --> C["Update Base Variable"] C --> F["End"] B -- Yes --> D["Hash to Specific Cell"] D --> E{"CAS Update\nSuccess?"} E -- Yes --> F E -- No --> G["Contention Detected"] G --> H["Try Expand Cells\nor Rehash"] H --> B

方案对比:选择最适合的工具

在面对不同的并发场景时,我们需要在 AtomicLongsynchronizedLongAdder 之间做出选择。下表详细对比了它们的特性:

方案 实现机制 优点 缺点 适用场景
AtomicLong CAS 自旋(无锁) 实现简单,非阻塞 高并发下 CPU 自旋严重,吞吐量低 低并发竞争,或需要全局唯一实时序列号
synchronized 重量级锁(JDK 1.6 后优化) 语法简单,JVM 优化后性能尚可 线程阻塞涉及上下文切换,有资源开销 一般同步控制,非极度高频的计数
LongAdder 分段累加(Cell 数组) 极高并发下吞吐量极高 sum() 并非强一致性(统计期间可能有变动) 高并发统计、计数、监控上报场景

实操指南:如何使用 LongAdder

在单机高并发环境下,优先使用 LongAdder 替代 AtomicLong。以下是具体的使用步骤。

1. 创建计数器实例

引入 java.util.concurrent.atomic.LongAdder 包。实例化 一个 LongAdder 对象用于后续操作。

import java.util.concurrent.atomic.LongAdder;

// 创建一个 LongAdder 计数器,初始值默认为 0
LongAdder counter = new LongAdder();

2. 执行累加或递减操作

调用 increment() 方法将计数加 1,或使用 add(delta) 方法增加指定数值。如果需要减法,调用 decrement() 或传入负数。

// 模拟多线程环境下的计数
Runnable task = () -> {
    // 增加计数
    counter.increment(); 
    // 等同于 counter.add(1);

    // 如果需要减少计数
    // counter.decrement();
};

3. 获取当前统计结果

调用 sum() 方法获取当前的累加总值。该方法会遍历所有 Cell 并求和,返回一个 long 类型结果。

// 获取最终的总计数
long totalCount = counter.sum();
System.out.println("Total Count: " + totalCount);

注意sum() 方法在计算时返回的是一个快照近似值,如果在求和过程中有其他线程正在修改 Cell,可能会导致统计结果不完全精准(例如漏算最后几次修改),但对于统计计量场景(如QPS、TPS),这种微小的误差通常是可以接受的。


代码示例:多线程性能对比

下面的代码模拟了 1000 个线程,每个线程执行 10 万次自增操作,展示了 LongAdder 的基本用法。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {
    // 定义 LongAdder 计数器
    static LongAdder longAdder = new LongAdder();
    // 定义 AtomicLong 计数器用于对比
    static AtomicLong atomicLong = new AtomicLong(0);

    public static void main(String[] args) throws InterruptedException {
        int threadCount = 1000;
        int timesPerThread = 100000;

        // 测试 LongAdder
        long start = System.currentTimeMillis();
        testLongAdder(threadCount, timesPerThread);
        long end = System.currentTimeMillis();
        System.out.println("LongAdder Result: " + longAdder.sum() + ", Time: " + (end - start) + "ms");

        // 测试 AtomicLong
        start = System.currentTimeMillis();
        testAtomicLong(threadCount, timesPerThread);
        end = System.currentTimeMillis();
        System.out.println("AtomicLong Result: " + atomicLong.get() + ", Time: " + (end - start) + "ms");
    }

    private static void testLongAdder(int threadCount, int times) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            service.submit(() -> {
                for (int j = 0; j < times; j++) {
                    // 执行递增
                    longAdder.increment();
                }
            });
        }
        service.shutdown();
        service.awaitTermination(1, java.util.concurrent.TimeUnit.MINUTES);
    }

    private static void testAtomicLong(int threadCount, int times) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            service.submit(() -> {
                for (int j = 0; j < times; j++) {
                    // 执行递增
                    atomicLong.incrementAndGet();
                }
            });
        }
        service.shutdown();
        service.awaitTermination(1, java.util.concurrent.TimeUnit.MINUTES);
    }
}

补充说明:分布式场景的扩展

上述 LongAdder 仅适用于单机 JVM 内存计数。在分布式微服务架构中,如果需要跨服务统计全局计数,使用 基于 Redis 的 Redisson 框架提供的 RLongAdder 对象。它采用了与 JDK LongAdder 类似的接口和原理,性能远高于分布式锁实现的 AtomicLong

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文