简介

LongAdder是Java8新增的一个原子操作类,主要用于高并发下的long类型计数和累加操作。LongAdder使用一个或多个变量的总和来维护计数器的值,并且在多线程竞争下,这个变量集可以动态增长。
**

LongAdder与AtomicLong

LongAdder与AtomicLong相比:在没有并发的情况下,两者具有相似的特征;但是在高并发情况下,LongAdder的效率明显高于AtomicLong,但是会使用更多的空间。

先来看下AtomicLong为什么在高并发下效率会比较低。AtomicLong在自增的时候会调用Unsage#getAndAddLong()方法,该方法在cas失败时会进行自旋,直至成功。在高并发下,cas失败的几率非常高,会浪费大量的时间,也就降低了效率。

  1. //sun.misc.Unsafe#getAndAddLong
  2. public final long getAndAddLong(Object o, long offset, long delta) {
  3. long v;
  4. do {
  5. v = getLongVolatile(o, offset);
  6. } while (!compareAndSwapLong(o, offset, v, v + delta));
  7. return v;
  8. }

那么LongAdder是如何做到高效率的呢?
相对于AtomicLong的单个value计数,LongAdder在并发下,会使用一个Cell数组(并且可以动态扩容)来帮助计数,即使用多个value,不同的线程可以在不同的Cell上进行计数,从而减少了线程竞争,提高了并发效率。本质是使用空间换时间的思想。

LongAdder与Striped64

LongAdder的实现主要依赖于Striped64类,Striped64是Java8中新增的用来支持累加器的并发组件,Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base变量和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面。

Cell类

先来看一下上文提到的Cell类,可以看到,Cell类只定义了一个value字段和cas方法,相当于一个简易版的AtomicLong类。
ps: Contended注解目的是为了防止变量的伪共享

  1. @sun.misc.Contended static final class Cell {
  2. volatile long value;
  3. Cell(long x) { value = x; }
  4. final boolean cas(long cmp, long val) {
  5. return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
  6. }
  7. private static final sun.misc.Unsafe UNSAFE;
  8. private static final long valueOffset;
  9. static {
  10. try {
  11. UNSAFE = sun.misc.Unsafe.getUnsafe();
  12. Class<?> ak = Cell.class;
  13. valueOffset = UNSAFE.objectFieldOffset
  14. (ak.getDeclaredField("value"));
  15. } //省略catch
  16. }
  17. }

Striped64类

成员变量

Striped64_field.png

核心方法

LongAdder#add

先看一下LongAdder是怎么使用Striped64的成员变量以及方法来进行累加的。
LongAdder_add.png
调用到Striped64#longAccumulate方法的情况:

  1. cells为空,但是casBase失败了,说明base的累加发生了竞争,则需要longAccumulate方法初始化cells。
  2. cells不为空,但是本次线程对应的cell为空,则通过longAccumulate方法创建cell。
  3. cells不为空,本次线程对应的cell也不为空,但是对该cell的cas操作失败了,则通过longAccumulate方法重新计算hash并重试,或对cells数组进行扩容
    Striped64#longAccumulate
    接下来看一下Striped64#longAccumulate方法的具体实现。Striped64_longAccumulate.png

LongAdder和AtomicLong性能对比

LongAdderAndAtomicLongTest.png
如上图所示,测试了单线程、多线程下,2000ms时间内,AtomicLong和LongAdder的累加数量。
结果如下所示:
LongAdderAndAtomicLongTest_result.png

other

代码