LongAdder源码解析
大致描述:
当并发较小时,通过cas更新base值即可;
随着并发量增加,cas更新base出现失败的情况,这是开始初始化cells(Cell[]),将增加的值存在cells中;后续对非空的cells位置访问,同样做cas更新操作。当cells数组某些热点位置的并发更新压力过大,会对cells数组进行扩容,分散热点。
最终获取结果为
b a s e + ∑ i = 0 c e l l s . l e n g t h − 1 c e l l s [ i ] base + \sum_{i=0}^{cells.length-1}{cells[i]} base+i=0∑cells.length−1cells[i]
Note
// CPU核数,Cell数组的长度限制
NCPU
// Cell[],不为空时,长度为2^n
cells
// 基础的值
base
// 调整大小和/或创建单元格时使用自旋锁(通过 CAS 锁定)
cellsBusy
// 更新cells是否发生冲突
uncontended
-
访问量较低时,并发量小,直接cas更新base的值
-
随着并发量的增加,cas更新base的值出现false的情况,此时cells数组还是null进入到longAccumulate方法。
cells数组初始化,根据当前线程的hash值,将该次的增量放入到cells数组中(h & (cells.length -1)等价于h % cells.length)
双重检测针对的特殊场景:
线程A进了第一个if判断和第二个if判断,在执行第二个if中对cells引用赋值之前,线程B仅执行过了第一个if的cells == as判断(指令重排),cellsBusy == 0 和 casCellsBusy()暂未执行,若没有双重检查,线程B进入以后,会对cells重新赋值,那么线程A的操作就丢失了。
-
后续并发线程在base上cas更新失败时,对cells添加值,分为以下两种情况
- 向为空的cells位置设置值。
进入到longAccumulate方法
通过cas获取得到锁,对cells数组对应的位置设置增量
-
向已经有值的位置作cas更新操作。
-
后续在高并发下,对cells数组做cas增量的更新时,发生冲突导致cas失败时,uncontended字段置为false,进入longAccumulate方法。
重新获取hash值,向新的cells位置作cas更新操作,根据这次cas操作的结果走向两个不同的分支:
-
若这次cas更新操作成功的话,则到此结束(重新hash后,也是可能找到一个空的位置去插入增量,这里以向一个已经有值的cells中的位置做cas更新操作举例)
-
若此次cas更新仍然失败,就要对该cells数组进行扩容了(其实还有一次重hash的机会,后面介绍)
扩容后,重复1、2分支的情况(cells数组扩容后,h & (cells.length -1)的值与之前可能不再相同了),成功则结束,失败则继续扩容。
-
后续的并发可能还会带来扩容操作,cells数组的长度也不断变大。
当cells的长度大于或等于cpu核数以后,再扩容也没有意义了,去重新获取hash值,重新在去做cas。
上图的第二个分支
可以看成一个缓冲,在扩容之前去重hash,再去试一次cas。
执行流程图: