1 LongAdder结构分析
1.1 为什么要有LongAdder
AutomicLong底层使用了CAS操作来控制并发的。在并发量级比较小的情况下,线程冲突的概率比较小,自旋次数少。但是,高并发的情况下,多个线程同时进行自旋操作,就会出现大量失败并一直自旋的情况,这个时候AutomicLong的性能就下降了。所以引入了LongAdder,解决高并发环境下AtomicLong自旋瓶颈的问题。
1.2 Longadder的结构
其结构如下,当线程不存在竞争的时候,首先将值写入到base中,当线程之间有竞争时会通过和HashMap一样的哈希算法,内部维护了一个base值和一个cell数组,写入到cell数组的一个槽中。
把一个变量拆成多份,变为多个变量,有些类似于 ConcurrentHashMap 的分段锁的例子。如下图所示,把一个Long型拆成一个base变量外加多个Cell,每个Cell包装了一个Long型变量。当多个线程并发累加的时候,如果并发度低,就直接加到base变量上;如果并发度高,冲突大,平摊到这些Cel上。在最后取值的时候,再把base和这些Cell求sum运算。
2 源码分析
2.1 LongAdder源码
LongAdder类本身,只有一个空参构造和一个add()方法,主要继承了Striped64并实现了Serializable
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
public LongAdder() {
}
public void add(long x) {
//....
}
- 分析add方法
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);
}
}
public void add(long x) {
//as 表示cells引用
//b 表示获取的base值
//v 表示 期望值
//m 表示 cells 数组的长度减1
//a 表示当前线程命中的cell单元格
Cell[] as; long b, v; int m; Cell a;
//条件一((as = cells) != null):true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
// false->表示cells未初始化,当前所有线程应该将数据写到base中
//条件二(!casBase(b = base, b + x)):false->表示当前线程cas替换数据成功,
// true->表示发生竞争了,可能需要重试 或者 扩容
if ((as = cells) != null || !casBase(b = base, b + x)) {
//什么时候会进来? ||(两个条件满足一个即可)
//1.true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
//2.true->表示发生竞争了,可能需要重试 或者 扩容
//true->未竞争 false->发生竞争
boolean uncontended = true;
//条件一:(as == null || (m = as.length - 1) < 0;条件二:(a = as[getProbe() & m]) == null;条件三:!(uncontended = a.cas(v = a.value, v + x))
//条件一:true->说明 cells 未初始化,说明上面那个if的条件二为true,也就是多线程写base发生竞争了
// false->说明 cells 已经初始化了,当前线程应该是 找自己的cell 写值
//条件二:说明:getProbe()可以理解成获取当前线程的hash值;m表示cells长度减1,cells长度一定是2的次方数
// [getProbe() & m]就类似HashMap的"(length-1) & hash"。cells长度一定是2的次方的原因也类似HashMap 的长度为什么是2的幂次方
// true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
// false-> 说明当前线程对应的cell 不为空,说明 下一步想要将x值 添加到cell中。
//条件三:true->表示cas失败,意味着当前线程对应的cell 有竞争
// false->表示cas成功
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
//都有哪些情况会进来调用? ||(三个条件满足一个即可)
//1.true->说明 cells 未初始化,也就是多线程写base发生竞争了 [重试|初始化cells]
//2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
//3.true->表示cas失败,意味着当前线程对应的cell 有竞争 [重试|扩容]
longAccumulate(x, null, uncontended);
}
}
分析:
①1表示第一个if的第一个条件;①2表示第一个if的第二个条件
②1表示第二个if的第一个条件;②2表示第二个if的第二个条件;②3表示第二个if的第三个条件;
如果,①1为true,表示当前线程需要把数写到对应的cell中。来到第二个if,②1为false,
如果②2为false,计算出a(当前线程命中的cell单元格),然后在②3中将数据通过cas的方式添加,
如果添加成功,即②3为false,add方法结束
如果添加失败,即②3为true,就调用longAccumulate方法重试|扩容
如果,①2为false,表示当前线程cas替换数据成功。add方法结束
如果,①2为true,表示当前线程cas替换数据的时候发生竞争了,可能需要重试 或者 扩容
//返回当前总和。
//在没有并发更新的情况下调用会返回准确的结果,但在计算总和时发生的并发更新可能不会合并。
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;
}
2.2 Striped64源码
- 内部类Cell
@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;
//Unsafe的objectFieldOffset(...) 方法调用,就是为了找到AtomicInteger类中value属性所在的内存偏移量。
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/** CPU 数量,用于限制表大小 */
//表示当前计算机CPU数量,什么用? 控制cells数组长度的一个关键条件
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* 没有发生过竞争时,数据会累加到 base上 | 当cells扩容过程中,线程就不能往对应的单元格中写,需要将数据写到base中
*/
transient volatile long base;
/**
* 调整大小或创建单元格时使用的自旋锁(通过 CAS 锁定).
* 初始化cells或者扩容cells都需要获取锁,0 表示无锁状态,1 表示其他线程已经持有锁了
*/
transient volatile int cellsBusy;
/**
* CASes the cellsBusy field from 0 to 1 to acquire lock.
* 通过CAS方式获取锁
*/
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
/**
* Returns the probe value for the current thread.
* Duplicated from ThreadLocalRandom because of packaging restrictions.
* 获取当前线程的Hash值
*/
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
/**
* Pseudo-randomly advances and records the given probe value for the
* given thread.
* Duplicated from ThreadLocalRandom because of packaging restrictions.
* 重置当前线程的Hash值
*/
static final int advanceProbe(int probe) {
probe ^= probe << 13; // xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
-
重点理解longAccumulate方法
-
LongAdder中调用此方法的原因:(三者之一)
-
- 情况一:cells 未初始化,也就是多线程写base发生竞争了 [重试|初始化cells]
-
- 情况二:cells 已初始化,但是当前线程对应下标的cell为空,需要创建 longAccumulate 支持
-
- 情况三:表示cas失败,意味着当前线程对应的cell 有竞争 [重试|扩容]
-
说一下longAccumulate()方法的逻辑,都有哪些情况会调用?
1. 情况一:说明 cells 未初始化,也就是多线程写base发生竞争了[初始化cells]
会进入case2,当前线程拿到锁并进入扩容;
如果当前线程没有获取到锁或者其他线程已经初始化了,则进入case3,表示其它线程正在初始化cells,所以当前线程将值累加到base
2. 情况二、情况三都会进入case1,表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
情况二:说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
进入case1.1,创建new Cell
情况三:表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]
cell已经有了但是在写的时候发生了竞争,进入case1.2,这时wasUncontended为false,取反为true,进入该条件,将这个值设置为true,进行rehash
自旋重新进入case1,cell不为空的话进入case1.3尝试一次,成功则退出自旋循环,否则进入case1.5,将collide设置为true,再次rehash,
再次查看case1.3条件是否满足,不满足则会直接进入到case1.6,这里才是真正的扩容方法。
- 注意:这里的case1.4是数组的长度不能超过cpu的数量,因为一旦超过,cpu同一时间只能处理一个线程,超过的无法处理,会造成内存浪费
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value
* @param fn the update function, or null for add (this convention
* avoids the need for an extra field or function in LongAdder).
* @param wasUncontended false if CAS failed before call
*/
//都有哪些情况会调用?
//1.true-> cells 未初始化,也就是多线程写base发生竞争了[初始化cells]
// 执行CASE2
//2.true-> cells 已初始化,但是当前线程对应下标的cell为空,需要创建 longAccumulate 支持
// 执行CASE1.1
//3.true-> cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]
// 执行CASE1.3 失败 -> CASE1.5 -> rehash,即执行advanceProbe(h) -> 自旋 -> 执行CASE1.3 再失败 -> CASE1.6()因为CASE1.5中将设置扩容意向为true
// wasUncontended:只有cells初始化之后,并且当前线程 竞争修改失败,才会是false
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
//h 表示线程hash值
int h;
//条件成立:说明当前线程 还未分配hash值
if ((h = getProbe()) == 0) {
//给当前线程分配hash值
ThreadLocalRandom.current(); // force initialization
//取出当前线程的hash值 赋值给h
h = getProbe();
//为什么wasUncontended设置为true?
//代码执行到这里,说明线程没有分配hash值(hash=0)
//所以 当前线程 肯定是写入到了 cells[0] 位置(0 & 任意数 = 0)。 不把它当做一次真正的竞争
wasUncontended = true;
}
//表示扩容意向。 false 一定不会扩容;true 可能会扩容。
boolean collide = false; // True if last slot nonempty
//自旋
for (;;) {
//as 表示cells引用
//a 表示当前线程命中的cell
//n 表示cells数组长度
//v 表示 期望值
Cell[] as; Cell a; int n; long v;
//CASE1: 表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
if ((as = cells) != null && (n = as.length) > 0) {
//进入这个if,有下面两种情况
//2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
//3.true->表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]
//CASE1.1:true->表示当前线程对应的下标位置的cell为null,需要创建new Cell
if ((a = as[(n - 1) & h]) == null) {
//true->表示当前锁 未被占用; false->表示锁被占用
if (cellsBusy == 0) { // Try to attach new Cell
//拿当前的x创建Cell
Cell r = new Cell(x); // Optimistically create
//条件一:true->表示当前锁 未被占用; false->表示锁被占用
//条件二:true->表示当前线程获取锁成功; false->当前线程获取锁失败。
if (cellsBusy == 0 && casCellsBusy()) {
//是否创建成功的标记
boolean created = false;
try { // Recheck under lock
//rs 表示当前cells 引用
//m 表示cells长度
//j 表示当前线程命中的下标
Cell[] rs; int m, j;
//条件一 条件二 为true
//条件三:rs[j = (m - 1) & h] == null 为了防止其它线程初始化过该位置,然后当前线程再次初始化该位置,导致丢失数据
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
}
}
//扩容意向 强制改为了false
collide = false;
}
// CASE1.2:true -> wasUncontended:只有cells初始化之后,并且当前线程 竞争修改失败,才会是false
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//CASE 1.3:当前线程rehash过hash值,然后新命中的cell不为空
//true -> 写成功,退出循环
//false -> 表示rehash之后命中的新的cell 也有竞争
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//CASE 1.4:
//条件一:n >= NCPU 为 true->就会把扩容意向改为false,不再扩容; false-> 说明cells数组还可以扩容
//条件二:cells != as 为 true->其它线程已经扩容过了,当前线程rehash之后重试即可
else if (n >= NCPU || cells != as)
//扩容意向为false,表示不扩容了
collide = false; // At max size or stale
//CASE 1.5:
//!collide == true 设置扩容意向 为true 但是不一定真的发生扩容
else if (!collide)
collide = true;
//CASE 1.6:真正扩容的逻辑
//条件一:cellsBusy == 0 true->表示当前无锁状态,当前线程可以去竞争这把锁
//条件二:casCellsBusy true->表示当前线程 获取锁 成功,可以执行扩容逻辑
// false->表示当前时刻有其它线程在做扩容相关的操作。
else if (cellsBusy == 0 && casCellsBusy()) {
try {
//这里又判断了一次cells == as。 防止其它线程已经扩容了,当前线程再次扩容。cells的引用就会发生变化,导致数据流失
if (cells == as) { // Expand table unless stale
//数组长度扩容,得到新数组,新长度等于旧长度左移1位,等价于 新长度=旧长度*2
Cell[] rs = new Cell[n << 1];
//遍历旧数组,将旧数组对应位置的值放到新数组中
for (int i = 0; i < n; ++i)
rs[i] = as[i];
//将新数组的引用赋值给全局的cells
cells = rs;
}
} finally {
//释放锁
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//修改失败,就会重置当前线程Hash值,然后当前线程自旋,再次修改时,就会到不同的cell上
h = advanceProbe(h);
}
//CASE2:前置条件cells还未初始化,as 为null
//条件一:true 表示当前未加锁
//条件二:为什么cells == as?短路原则,当判断这个条件时,条件一为true,表示其他线程已经持有锁了
// 那么其它线程在执行下面的初始化方法,可能会在你给as赋值为null之后修改了 cells,所以这里再判断一次
//条件三:true 表示获取锁成功 会把cellsBusy = 1,false 表示其它线程正在持有这把锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
//这里又判断了一次cells == as。 防止其它线程已经初始化了,当前线程再次初始化 导致丢失数据
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
//CASE3:前面两个if中所有条件都为false才会执行到这,说明:
//1.当前cellsBusy加锁状态,表示其它线程正在初始化cells,所以当前线程将值累加到base
//2.cells被其它线程初始化后,当前线程需要将数据累加到base
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}