JAVA8 最快的原子操作类 ------- LongAdder
LongAdder
JDK1.8 时, java.util.concurrent.atomic 包中提供了一个新的原子类: LongAdder。 根据 Oracle 官方文档的介绍,LongAdder 在高并发的场景下会比它的前辈———AtomicLong 具有更好的性能,代价是消耗更多的内存空间。
AtomicLong 是利用了底层的 CAS 操作来提供并发性的,调用了 Unsafe 类的 getAndAddLong 方法,该方法是个 native 方法,它的逻辑是采用自旋的方式不断 更新目标值,直到更新成功。
在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但 是,高并发环境下,N 个线程同时进行自旋操作,会出现大量失败并不断自旋的 情况,此时 AtomicLong 的自旋会成为瓶颈。
这就是 LongAdder 引入的初衷——解决高并发环境下 AtomicLong 的自旋瓶 颈问题。
AtomicLong 中有个内部变量 value 保存着实际的 long 值,所有的操作都是 针对该变量进行。也就是说,高并发环境下,value 变量其实是一个热点,也就 是 N 个线程竞争一个热点。
LongAdder 的基本思路就是 分 散 热 点 ,将 value 值分散到一个数组中,不同 线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要 将各个槽中的变量值累加返回。
- 这种做法和 ConcurrentHashMap 中的“分段锁”其实就是类似的思路。
- LongAdder 提供的 API 和 AtomicLong 比较接近,两者都能以原子的方式对 long 型变量进行增减。
- 但是 AtomicLong 提供的功能其实更丰富,尤其是 addAndGet、 decrementAndGet、compareAndSet 这些方法。
- addAndGet、decrementAndGet 除了单纯的做自增自减外,还可以立即获取增减后的值,而 LongAdder 则需要做同步控制才能精确获取增减后的值。如果业务需求需要精确的控制计数,做计数比较,AtomicLong 也更合适。
- 另外,从空间方面考虑,LongAdder 其实是一种“空间换时间”的思想,从 这一点来讲 AtomicLong 更适合。
总之,低并发、一般的业务场景下 AtomicLong 是足够了。如果并发量很多, 存在大量写多读少的情况,那 LongAdder 可能更合适。适合的才是最好的,如果 真出现了需要考虑到底用 AtomicLong 好还是 LongAdder 的业务场景,那么这样 的讨论是没有意义的,因为这种情况下要么进行性能测试,以准确评估在当前业 务场景下两者的性能,要么换个思路寻求其它解决方案。
对于 LongAdder 来说,内部有一个 base 变量,一个 **Cell[]**数组。
- base 变量:非竞态条件下,直接累加到该变量上。
- Cell[]数组:竞态条件下,累加个各个线程自己的槽 Cell[i]中。
所以,最终结果的计算应该是
在实际运用的时候,只有从未出现过并发冲突的时候,base 基数才会使用 到,一旦出现了并发冲突,之后所有的操作都只针对 Cell[]数组中的单元 Cell。
而 LongAdder 最终结果的求和,并没有使用全局锁,返回值不是绝对准确的, 因为调用这个方法时还有其他线程可能正在进行计数累加,所以只能得到某个时刻的近似值,这也就是 LongAdder 并不能完全替代 LongAtomic 的原因之一。
而且从测试情况来看,线程数越多,并发操作数越大,LongAdder 的优势越 大,线程数较小时,AtomicLong 的性能还超过了 LongAdder。
其他新增特性
-
除了新引入 LongAdder 外,还有引入了它的三个兄弟类:LongAccumulator、 DoubleAdder、DoubleAccumulator。
-
LongAccumulator 是 LongAdder 的增强版。 LongAdder 只能针对数值的进行加减运算,而 LongAccumulator 提供了自定义的函数操作。
-
通过 LongBinaryOperator,可以自定义对入参的任意操作,并返回结果 (LongBinaryOperator 接收 2 个 long 作为参数,并返回 1 个 long)。
-
LongAccumulator 内部原理和 LongAdder 几乎完全一样。
-
DoubleAdder 和 DoubleAccumulator 用于操作 double 原始类型。