LongAdder和AtomicLong类似是用于多线程下来保证数据更新的原子性,AtomicLong主要是依赖CAS操作来保证原子性的,其方法本质是在循环中一直尝试CAS,直到成功时才退出循环,所以在线程竞争激烈的场景往往性能不是很好(尽管已经比使用悲观锁好的多);
LongAdder采用的是类似分治的思想,再遇到多个线程同时对数据进行更新时,会将数据分为多份更细粒度的子单位再更新,从而达到减少线程竞争的目的,额外的消耗是需要更多的空间;在ConcurrentHashMap
中也有它的身影,
源码实现
Striped64
是一个抽象类,里面的实现是LongAdder
操作方法的基础;该类里面维护了一个表只允许原子性操作并且是懒加载的,大小为2的幂次方;
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
AtomicLong
类的变体,只支持原始访问和CAS操作,这个就是LongAdder
中细粒度的单位;
/** cpu的数目,决定着细分cell的数量 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/** cells表,当非空时size是2的幂 */
transient volatile Cell[] cells;
/** 基本值,主要在没有竞争时使用,通过CAS更新 */
transient volatile long base;
/** 自旋锁,通过CAS锁定 */
transient volatile int cellsBusy;
/** 可以看到更新BASE时是用cas操作 */
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
/** 从0到1的情况表示获取锁 把cellsBusy置0表示释放锁*/
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
base属性就是没有线程冲突时累加的数值,在遇到线程竞争时才进行分治处理;
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
// 当h值为0时表示未初始化
ThreadLocalRandom.current(); // 强制初始化
h = getProbe();
wasUncontended = true;
}
//如果最后一个槽非空,则为真,也用于控制扩容,false重试。
boolean collide = false;
for (;;) { //for死循环
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
// 表已经初始化
if ((a = as[(n - 1) & h]) == null) {
// 如果所映射到的槽是空的
if (cellsBusy == 0) { //判断锁是否被使用
// 锁未被使用,乐观地创建并初始化cell。
Cell r = new Cell(x); // 乐观地创建
if (cellsBusy == 0 && casCellsBusy()) {
// 锁仍然是空闲的、且成功获取到锁
boolean created = false;
try {
// 在持有锁时再次检查槽是否空闲
Cell[] rs; int m, j;
if ((rs = cells) != null && //如果cells不为空
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 所映射的槽仍为空
rs[j] = r;// 关联 cell 到槽
created = true;
}
} finally {
cellsBusy = 0;// 释放锁
}
if (created)
break;// 成功创建cell并关联到槽,退出循环
continue; //走到这表示上面获取到锁时槽被占用了 需要重新循环申请锁
}
}
collide = false;// 锁被占用了,重试
}
// 槽被占用了
else if (!wasUncontended) // 已知CAS失败
wasUncontended = true; // 在重散列后继续,在当前槽的cell上尝试更新,重装散列表示重新刷新h值
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;;
// 表达到上限后(最大为CPU核数)就不会再尝试下面if的扩容了,只会重散列,尝试其他槽
else if (n >= NCPU || cells != as)
collide = false;
else if (!collide)
collide = true;
// 如果不存在冲突,则设置为存在冲突
else if (cellsBusy == 0 && casCellsBusy()) {
// 锁空闲且成功获取到锁
// 进到这里表示没有足够的槽添加了 需要进行扩容
try {
if (cells == as) { // 距上一次检查后表没有被改变,进行扩容
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; // 在扩容后的表上重试
}
// 没法获取锁,重散列,尝试其他槽
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
//未初始化表,在获取锁成功后初始化表
boolean init = false;
try {
if (cells == as) {
Cell[] rs = new Cell[2]; //初始化大小为2
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;// 成功初始化,已更新,跳出循环
}
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
//表未被初始化,可能正在被初始化,
//直接尝试cas设置base值,如果本次cas成功则退出循环,否则重新判断
break;
}
}
上面这段代码就是LongAdder
类处理多线程冲突下的分治操作,总体而言操作流程如下;
- 发生线程竞争,判断是否初始化过Table;如未初始化,会创建一个容量为2的Table,并且将value插入其中一个插槽;
- 第二次发生线程竞争时,会先根据
ThreadLocalRandom
的探针计算哈希值来寻找插槽,如果插槽为空,则插入插槽,如果插槽不为空,会尝试通过CAS更新该插槽的值; - 如果插槽不为空且CAS更新失败,则会尝试扩建Table,最多扩张到大于或等于最接近CPU核数的2的幂次方;
通过ThreadLocalRandom
的探针字段来用于每个线程的哈希码,为0意味着未初始化,发生线程冲突时,如果Table容量不能再扩大,且CAS操作失败,则会进行双重哈希,使用辅助哈希Marsaglia XorShift
尝试查找空闲插槽;
//Marsaglia XorShif随机数算法
static final int advanceProbe(int probe) {
probe ^= probe << 13; // xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
回到LongAdder
看下add
方法
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
// cells 不为空 或更新base的cas失败,也即出现了竞争。
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 || // as 为空表示cells未被初始化 || cells的长度为0
(a = as[getProbe() & m]) == null || // as[i]上的值为null,表示该位置没有被占用
!(uncontended = a.cas(v = a.value, v + x))) //cas 成功,将as[i]的值替换成 oldvalue + x
// 如果所映射的槽不为空,且成功更新则返回,否则进入复杂处理流程。
longAccumulate(x, null, uncontended);
}
}
可以看出在没有线程冲突时还是会先通过cas更新base值,极力避免进到Striped64
的复杂处理流程;
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
sum聚合操作就是将base值和Table中所有Cell值相加得出,下图可能有点不准确,但是思想是类似的;