最近发现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版,操作逐渐升级。
这种设计思想可以学习一手