AtomicLong 是不是该淘汰了?

Photo By Instagram sooyaaa

问题 5.

相信你一定记得学习并发编程的一个入门级例子,多个线程操作一个变量,累加 10000 次,最后结果居然不是 10000。后来你把这个变量换成了并发包中的原子类型变量 AtomicLong,完美的解决了并发问题。假如面试官问:还有更好的选择吗?LongAdder 了解过吗?你能对答如流吗?

我的答案

AtomicLong 是 Java 1.5 并发包中提供的一个原子类,他提供给了我们在多线程环境下安全的并发操作一个整数的特性。并且性能还可以,它主要依赖了 2 个技术,volatile 关键字和 CAS 原子指令,不知道这俩个技术的小伙伴参考往期的文章:ReentranLock 实现原理居然是这样?;AtomicLong 性能已经不错了,但是当在线程高度竞争的状况下性能会急剧下降,因为高度竞争下 CAS 操作会耗费大量的失败计算,因为当一个线程去更新变量时候发现值早已经被其他线程更新了。那么有没有更好的解决方案呢,于是 LongAdder 诞生了。

LongAdder 是 Java 1.8 并发包中提供的一个工具类,它采用了一个分散热点数据的思路。简单来说,Atomic 中所有线程都去更新内存中的一个变量,而 LongAdder 中会有一个 Cell 类型的数组,这个数组的长度是 CPU 的核心数,因为一台电脑中最多同时会有 CPU 核心数个线程并行运行,每个线程更新数据时候会被映射到一个 Cell 元素去更新,这样就将原本一个热点的数据,分散成了多个数据,降低了热点,这样也就减少了线程的竞争程度,同时也就提高了更新的效率。当然这样也带了一个问题,就是更新函数不会返回更新后的值,而 AtomicLong 的更新方法会返回更新后的结果,LongAdder 只有在调用 sum 方法的时候才会去累加每个 Cell 中的数据,然后返回结果。当然 LongAdder 中也用到了volatile 和 CAS 原子操作,所以小伙伴们一定要掌握这俩个技术点,这是面试必问的点。

既然说 LongAdder 的效率更好,那我们就来一段测试代码,小小展示一下 LongAdder 的腻害之处,请看如下:

public class AtomicLongTester {


    private static AtomicLong numA = new AtomicLong(0);
    private static LongAdder numB = new LongAdder();




    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i < 10001; i*=10) {
            test(false, i);
            test(true, i);
        }
    }




    public static void test(boolean isLongAdder, int threadCount) throws InterruptedException {
        long starTime = System.currentTimeMillis();
        final CountDownLatch latch = new CountDownLatch(threadCount);


        for (int i = 0; i < threadCount; i++) {
            new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 100000; i++) {
                        if (isLongAdder) {
                            numB.add(1);
                        } else {
                            numA.addAndGet(1);
                        }
                    }


                    latch.countDown();
                }
            }).start();
        }


        // 等待所有运算结束
        latch.await();


        if (isLongAdder) {
            System.out.println("Thread Count=" + threadCount + ", LongAdder cost ms=" + (System.currentTimeMillis() - starTime) + ", Result=" + numB.sum());
        } else {
            System.out.println("Thread Count=" + threadCount + ", AtomicLong cost ms=" + (System.currentTimeMillis() - starTime) + ", Result=" + numA.get());
        }


        numA = new AtomicLong(0);
        numB = new LongAdder();
    }


}

实验结果大致如下:

Thread Count=1, AtomicLong cost ms=9, Result=100000
Thread Count=1, LongAdder cost ms=13, Result=100000
Thread Count=10, AtomicLong cost ms=14, Result=1000000
Thread Count=10, LongAdder cost ms=41, Result=1000000
Thread Count=100, AtomicLong cost ms=111, Result=10000000
Thread Count=100, LongAdder cost ms=45, Result=10000000
Thread Count=1000, AtomicLong cost ms=1456, Result=100000000
Thread Count=1000, LongAdder cost ms=379, Result=100000000
Thread Count=10000, AtomicLong cost ms=17452, Result=1000000000
Thread Count=10000, LongAdder cost ms=3545, Result=1000000000

从上面的结果可以看出来,当线程竞争率比较低的时候 AtomicLong 效率还是优于 LongAdder 的,但是当线程竞争率增大的时候,我们可以看出来 LongAdder 的性能远远高于 AtomicLong。

因此在使用原子类的时候,我们要结合实际情况,如果竞争率很高,那么建议使用 LongAdder 来替代 AtomicLong。说到 LongAdder 也不得的说一说 volatile 带来的伪共享问题,对伪共享感兴趣的同学欢迎关注后续的文章,我们会在后续的文章中探讨这个问题。

以上即为昨天的问题的答案,小伙伴们对这个答案是否满意呢?欢迎留言和我讨论。

又要到年末了,你是不是又悄咪咪的开始看机会啦。为了广大小伙伴能充足电量,能顺利通过 BAT 的面试官无情三连炮,我特意推出大型刷题节目。每天一道题目,第二天给答案,前一天给小伙伴们独立思考的机会。

点下“在看”,鼓励一下?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值