Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

最近发现JUC包里除了AtomicLong外还有LongAdder,所以打算研究一下它俩的异同。

AtomicLong

AtomicLong是JUC包中的原子类,通过CAS来实现long类型的加减。

那么既然都有AtomicLong类了,为什么还要有一个LongAdder类?因为从名字来看,LongAdder也是用来操作long类型的。

LongAdder的设计思想

先翻译一段官方文档里的解释:

LongAdder是通过多个变量一起来维护一个long型总和。什么意思呢?它主要是为了计算一些统计信息的,在多线程竞争的场景下,它给每个线程都分配一个变量,每个线程各自修改自己的变量,然后它有个sum()方法,可以计算所有变量的总和,通过这种方式来减少多线程之间的竞争。

当多个线程去更新一个公有的sum总和时,我们更偏向于用LongAdder而非AtomicLong. 这两个类特性相似,但是在多线程竞争激烈的场景,LongAdder具有更好的性能(这一点也可想而知,毕竟AtomicLong使用的是CAS)。

上面的描述也就基本上解释了LongAdder的缘起缘灭了,它主要是为统计而生,而非那种细粒度的同步控制。

其实这个也就相当于一种分治,非中央集权而是分而治之,让每个线程维护自己的那个变量,最后综合统计一下。

详情

我们在这里从源码角度讨论一下LongAdder.

先看下它的继承关系:

1
public class LongAdder extends Striped64 implements Serializable

它最核心的一个方法就是add()方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}

看着很简单,但里面的逻辑判断却不少。先简单介绍下这里面的一些陌生类型和方法。

  • Cell[]Cell类里面其实就是维护了一个变量,这个数组用来存在每个线程的自己维护的变量。具体细节是这样的:对每个线程计算hash,将得到的hash值作为Cell数组的下标。

  • casBase():对base变量进行CAS,什么是base变量呢?当有竞争是使用Cell[]数组给每个线程维护一个变量,当没有竞争是LongAdder就只操作一个base变量就可以了。

  • getProbe():得到当前线程对应的哈希值,再和数组长度进行与运算就得到了对应的下标(有没有一点hashmap的影子?).

  • longAccumulate():当基本的操作都失败时,执行这个方法。

为了方便理解代码逻辑,我画了一个流程图:

QwURIS.jpg

可以看到大概流程是先操作Cell数组,数组空的话再操作base,如果都没成功再考虑longAccumulate(),虽然我也没仔细研究longAccumulate()里的代码,但是大概可以猜到,它应该是上述未成功操作的plus版,操作逐渐升级。

这种设计思想可以学习一手

评论