转载

AtomicLong 是不是该淘汰了?

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 的面试官无情三连炮,我特意推出大型刷题节目。 每天一道题目,第二天给答案,前一天给小伙伴们独立思考的机会。

AtomicLong 是不是该淘汰了?

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

原文  https://mp.weixin.qq.com/s/RQJZL9kFIB95pI6xLLjUgA
正文到此结束
Loading...