最近发现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 | /** |
看着很简单,但里面的逻辑判断却不少。先简单介绍下这里面的一些陌生类型和方法。
Cell[]
:Cell
类里面其实就是维护了一个变量,这个数组用来存在每个线程的自己维护的变量。具体细节是这样的:对每个线程计算hash
,将得到的hash
值作为Cell
数组的下标。casBase()
:对base
变量进行CAS,什么是base
变量呢?当有竞争是使用Cell[]
数组给每个线程维护一个变量,当没有竞争是LongAdder
就只操作一个base
变量就可以了。getProbe()
:得到当前线程对应的哈希值,再和数组长度进行与运算就得到了对应的下标(有没有一点hashmap
的影子?).longAccumulate()
:当基本的操作都失败时,执行这个方法。
为了方便理解代码逻辑,我画了一个流程图:
可以看到大概流程是先操作Cell
数组,数组空的话再操作base,如果都没成功再考虑longAccumulate
(),虽然我也没仔细研究longAccumulate()
里的代码,但是大概可以猜到,它应该是上述未成功操作的plus
版,操作逐渐升级。
这种设计思想可以学习一手