LongAdder的应用和获取准确值的方法

本文讨论了在高并发场景下使用LongAdder处理数据更新的问题,提出了一种利用双LongAdder实现数据同步的解决方案,确保在获取值时的准确性。作者分享了问题分析、解决方案及其实现方式,并提及其他可能的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目场景:

使用LongAdder本地保存数据,定时的去拉取本地缓存中的数据更新到数据库。


问题描述:

因为Mentor要求我保存数据的时候要考虑高并发和性能问题,所以我也就想到了使用LongAdder。LongAdder在添加的时候性能会比AtomicInteger更好,原因在于其longAccumulate()方法和getAndAddInt()方法的区别,有兴趣的可以自己去看看源码。不得不说每次看完大神Doug Lea写的代码,就两个字牛逼。虽然LongAdder的性能比AtomicInteger更好,但是LongAdder获取的值不是准确值。这个问题我也咨询了一下Mentor,Mentor表示肯定是不能丢数据,可以用一定的技术解决这个问题,那不用说了我必须要做一个有技术的人。

public void getSum() {
        LongAdder longAdder = new LongAdder();
        longAdder.sum();
}

原因分析:

这里可以先看下LongAdder的sum()方法,方法里面是先获取到base的值,再通过遍历cells数组,最后累加base和数组的值返回。

public long sum() {
        Cell[] var1 = this.cells;
        long var3 = this.base;
        if (var1 != null) {
        	//遍历cells数组
            for(int var5 = 0; var5 < var1.length; ++var5) {
                Cell var2;
                if ((var2 = var1[var5]) != null) {
                    var3 += var2.value;//累加
                }
            }
        }
	
        return var3;
}

在并发环境下这个方法肯定会出现获取的值小于添加的值,例如:一个线程正在调用sum()方法获取值,另外一个线程在调用add()方法添加值,那添加的值可能就落在你之前已经遍历过的数组下标或者是base中,最终累加得到的sum值肯定少加了一些值是不正确的。


解决方案:

这个问题网上搜了一下,但是没有找到解决办法,最后想到了一个比较简单的办法。
大概的代码流程:

	private static LongAdder longAdder1 = new LongAdder();
    private static LongAdder longAdder2 = new LongAdder();
    private static LongAdder data = longAdder1;//默认longAdder1
    private static boolean flag = true;//开关
    public void add() {
        data.increment();
    }
    public long getSum() {
        long sum = 0;
        if (flag) {
            data = longAdder2;
            sum = longAdder1.sum();
            flag = false;
        } else {
            data = longAdder1;
            sum = longAdder2.sum();
            flag = true;
        }
        return sum;
    }

流程图:
在这里插入图片描述
这个方法其实就是类似于一个开关按钮,一开始默认在longAdder1进行添加,当我要获取值的时候,就按下开关让它在另外一边longAdder2进行添加,所以就能保证在longAdd1上获取的是正确值也不会影响添加,代码逻辑也比较简单,缺点是空间开销更大。暂时只能想到这个办法解决问题,有时间再想想还能不能优化吧。

### LongAdder 的用法与实现细节 `LongAdder` 是 Java 并发包 `java.util.concurrent.atomic` 中的一个类,旨在解决高并发场景下频繁更新共享变量时的竞争问题。它通过减少竞争来提高性能,特别适用于计数器场景。 #### 高效的设计理念 `LongAdder` 使用了一种称为 **分条累加(Striped Accumulation)** 的技术[^1]。其核心思想是将多个线程的增量操作分布到不同的内部单元上执行,从而降低锁争用的可能性。最终的结果可以通过汇总这些单元的获得。 #### 主要方法概述 以下是 `LongAdder` 提供的一些常用方法及其功能: - **`increment()`**: 将当前增加 1。 - **`decrement()`**: 将当前减去 1。 - **`add(long x)`**: 增加指定数。 - **`sum()` 或者 `longValue()`**: 返回累积计算后的总。 需要注意的是,在多线程环境中调用 `sum()` 方法会触发一次同步的操作以确保数据一致性[^2]。 #### 实现机制剖析 为了提升效率并减少冲突,`LongAdder` 维护了一个数组结构用于存储各个独立的部分求结果。每当某个线程尝试修改该对象时,如果发现对应槽位未被占用,则直接写入;否则采用 CAS(Compare And Swap)算法寻找新的位置记录变化量。这种策略有效缓解了传统原子类型如 AtomicLong 所面临的瓶颈问题——即当大量线程同时访问同一内存地址时产生的激烈争夺现象[^3]。 下面展示如何创建以及使用一个简单的例子: ```java import java.util.concurrent.atomic.LongAdder; public class Main { public static void main(String[] args) throws InterruptedException { final int THREAD_COUNT = 10; Thread[] threads = new Thread[THREAD_COUNT]; LongAdder counter = new LongAdder(); for(int i=0;i<THREAD_COUNT;i++) { threads[i]=new Thread(()->{ for(int j=0;j<1000;j++) { counter.increment(); } }); threads[i].start(); } for(Thread t : threads){ t.join(); // wait until all thread finish their job. } System.out.println("Total count:"+counter.sum()); } } ``` 上述程序展示了利用 `LongAdder` 来统计来自不同线程贡献次数的方式,并验证最后得到总数是否正确无误。 #### 性能对比分析 相较于传统的 `AtomicLong`, 在极高频率读写的条件下,由于引入了额外的空间开销换取时间上的优化,因此可以观察到显著优于前者的表现特性。然而对于低负载或者单一线程应用场合来说,可能并不会体现出明显优势甚至稍逊一筹因为存在初始化成本等因素影响。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值