LongAdder核心源码如下
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[] as:暂存Cell数组
long b:暂存base的值
long v:暂存a的值
int m:暂存as数组的长度
Cell a:根据getProbe()与Cell数组的长度做&操作得到数组中的一个Cell,这里留个疑问,getProbe()是干啥的?
1、首先判断cells数组不为空,或者对base值做cas操作失败,则进入if的逻辑。
2、第二个if中判断as为空,或者随机的Cell为空,或者对随机的Cell做cas操作失败,则进入longAccumulate方法,这里需要注意一点,我们可以看到getProbe()获取随机数的方法与cell数组的长度m做的是&操作,通过以往的源码经验可以知道,这个m必定是-1,我们可以带着这个疑问进入longAccumulate方法。
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
//cells数组不为空
if ((as = cells) != null && (n = as.length) > 0) {
//如果取到的Cell为空,则初始化Cell
if ((a = as[(n - 1) & h]) == null) {
//先判断cellBusy是否为0,cellBusy这里起到了乐观锁的作用
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
//再次判断cellBusy是否为0,并且通过cas给cellBusy加锁
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
//这里再次判断cell数组中对应的数据是否为空,有点单例模式里面双重锁校验的意思在里面,如果不为空,才设置值
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
//释放锁
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
//外层传进来的,对cell的cas失败就会是false,这里先置为true,这里对wasUncontended判断就相当于一种拦截,默认如果前面已经发生锁竞争,cas失败,则直接先进行再hash操作
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//经历了前面的初始化,这里的cell肯定不为空,直接做cas操作,如果成功则跳出循环
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//cell数组长度大于NCPU,或者发生了变化,则设置collide为false,直接进行再hash
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
//如果发生了锁竞争,也直接进行再hash
else if (!collide)
collide = true;
//如果拿到了cellBusy锁且cells数组地址无变化,则扩充cells数组
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//再hash
h = advanceProbe(h);
}
//如果cell为空则先初始化
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
//如果没拿到cellsBusy锁,则还是先更新base
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
3、可以看到longAccumulate方法非常的长。
首先是判断getProbe()是否为0,如果为0就初始化一下,似乎这个值是不能为0的。
具体的可以查看代码上面的注释,大体上就是通过各种cas操作来实现数据递增,cell数组初始化长度为2,可以看到在扩充时是二进制左移一位,即原来的长度乘以2,符合我们前面的猜想。
4、cell数组定义成2^n的原因,定义成2^n后实际的下标就是0-2^n-1,对一个数跟2^n-1进行&的操作就相当于对2^n进行%操作,只是&会快一些,所以我们能看到很多源码长度都是用的2^n。