最近在看阿里的 Sentinel 的源码的时候。发现使用了一个类 LongAdder 来在并发环境中计数。这个时候就提出了疑问,JDK 中已经有 AtomicLong 了,为啥还要用 LongAdder ? AtomicLong 已经是基于 CAS 的无锁结构,已经有很好的并发表现了,为啥还要用 LongAdder ?于是赶快找来源码一探究竟。
AtomicLong 的缺陷
大家可以阅读我之前写的 JAVA 中的 CAS 详细了解 AtomicLong 的实现原理。需要注意的一点是,AtomicLong 的 Add() 是依赖自旋不断的 CAS 去累加一个 Long 值。如果在竞争激烈的情况下,CAS 操作不断的失败,就会有大量的线程不断的自旋尝试 CAS 会造成 CPU 的极大的消耗。
LongAdder 解决方案
通过阅读 LongAdder 的 Javadoc 我们了解到:
This class is usually preferable to {@link AtomicLong} when multiple threads update a common sum that is used for purposes such as collecting statistics, not for fine-grained synchronization control. Under low update contention, the two classes have similar characteristics. But under high contention, expected throughput of this class is significantly higher, at the expense of higher space consumption.
大概意思就是,LongAdder 功能类似 AtomicLong ,在低并发情况下二者表现差不多,在高并发情况下 LongAdder 的表现就会好很多。
LongAdder 到底用了什么黑科技能做到高性比 AtomicLong 还要好呢呢?对于同样的一个 add() 操作,上文说到 AtomicLong 只对一个 Long 值进行 CAS 操作。而 LongAdder 是针对 Cell 数组的某个 Cell 进行 CAS 操作 ,把线程的名字的 hash 值,作为 Cell 数组的下标,然后对 Cell[i] 的 long 进行 CAS 操作。简单粗暴的分散了高并发下的竞争压力。
LongAdder 的实现细节
虽然原理简单粗暴,但是代码写得却相当细致和精巧。
在 java.util.concurrent.atomic 包下面我们可以看到 LongAdder 的源码。首先看 add() 方法的源码。
看到这个 add() 方法,首先需要了解 Cell 是什么?