相信你一定记得学习并发编程的一个入门级例子,多个线程操作一个变量,累加 10000 次,最后结果居然不是 10000。后来你把这个变量换成了并发包中的原子类型变量 AtomicLong,完美的解决了并发问题。假如面试官问:还有更好的选择吗?LongAdder 了解过吗?你能对答如流吗?
AtomicLong由来
AtomicLong 是 Java 1.5 并发包中提供的一个原子类,他提供给了我们在多线程环境下安全的并发操作一个整数的特性。并且性能还可以,它主要依赖了 2 个技术,volatile 关键字和 CAS 原子指令,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.sm());
} else
System.out.printn("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。