关于LongAdder的一点思考

LongAdder是jdk 1.8引入的一个类,宣称比AtomiLong更高效。它内部有一个基本数base和一个cell数组,在高并发的情况下各个线程将值存放在了数组中,在低并发的情况下直接在一个base数上做计算,取值的时候把基本数和cell数组中的值做累加返回。LongAdder采用了类似分段锁的设计,降低了竞争。

其实看到取值的时候我是懵的,取值的代码如下:

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

注释里说了,当没有并发更新的时候,这个值是准确的。当有并发更新的时候,这个值就不准确了。原因很简单,虽然base和cells都是volatile类型,保证了可见性,然而我累加到一半的时候,base因为并发更新而改变了,可我已经累加过了,拿到的结果肯定没有包括这期间新增的值。

令人沮丧的是,在并发期间,这个真实的值几乎是不可用的。虽然LongAdder存起来了各个确定的值,但是我却拿不到这个准确的和。

因此LongAdder,应该只适用于对计数不要求那么精确(虽然它是精确的保存了所有值的),或者只关心最终的值(并发已经结束,可以拿到最终的结果)。在阿里巴巴JAVA开发手册中,稍微提到了LongAdder在做类似count++操作方面的优势,如果只是想统计下最后的个数,那么LongAdder肯定是最好的选择。
在这里插入图片描述
下面附上LongAdder和AtomicLong在做count++上面的比较,测试代码如下:

@Data
@Slf4j
public class LongAdderTest{

    public static final int COR = 1000;

    public static final int MAX = 10000;

    public static final int THREAD_COUNT = 1000; // 通过修改并发线程数和循环次数来测试

    public static final int LOOP = 1000000; // 通过修改并发线程数和循环次数来测试

    public static void main(String[] args) throws Exception{
        ExecutorService pool = new ThreadPoolExecutor(COR,MAX,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>());

        final LongAdder adder = new LongAdder();
        final AtomicLong count = new AtomicLong();
        CompletionService completionService = new ExecutorCompletionService<>(pool);
        long start = System.currentTimeMillis();
        for(int i =0;i<THREAD_COUNT;i++){
            completionService.submit(() -> {
                for (int j = 0; j < LOOP; j++) {
                    //count.incrementAndGet();
                    adder.increment();
                }
                return 1L;
            });
        }

        for (int i = 0; i < THREAD_COUNT; i++) {
            Future<Long> future = completionService.take();
            future.get();
        }
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

        pool.shutdown();
    }

我们固定循环100W次,然后改变并发线程数,下面是测试结果:

101001000
AtomicLong441ms2.931s28.851s
LongAdder203ms706ms6.526s

可以看到,在低并发的时候两者区别并不是很大,在高并发的时候两者耗时就不是一个数量级了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值