===================
package leetcode0606.JUC.LongAdder;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.LongBinaryOperator;
public class LongAdderDebug01 extends Striped64{
public static void main(String[] args) {
}
// add()方法
public void add(long x) {
/*
as ---- transient volatile Cell[] cells 的引用
b ---- 表示获取的base值
v ---- 表示期望值
m ---- as.length - 1
a ---- 当前线程命中的cell单元格
*/
Striped64.Cell[] as; long b, v; int m; Striped64.Cell a;
/*
条件一:为true 当前cells数组已经初始化过了,当前线程应该讲数据写入到对应的cell中
为false 当前cells数组未初始化,当前线程应该将数据写入到base中
条件二:true 当前线程 CAS成功
false CAS失败,发生了竞争,可能需要重试 或者 扩容
*/
we if ((as = cells) != null || !casBase(b = base, b + x)) {
/*
只有当以下两种情况代码才会往下走:
(as = cells) != null 为true 当前cells数组已经初始化过了,当前线程应该讲数据写入到对应的cell中
!casBase(b = base, b + x) 为false CAS失败,发生了竞争,可能需要重试 或者 扩容
*/
boolean uncontended = true; // 无竞争的
// 条件一 as == null || (m = as.length - 1) < 0 这两个条件可以合并为一个,都是为了说明cells数组还未初始化
// 若为true说明cells数组未初始化,同时与第一个if判断条件映射,即多线程写base发生了竞争,且cells数组未初始化
// 若为false则说明cells已经初始化,当前线程该找自己的cell单元格进行写操作
// 条件二 (a = as[getProbe() & m]) == null 获取当前的hash,并且&m 得到cell的位置
// 若为true 说明当前线程对应的cell为空
// 若为false 说明此位置已有值,我只需要在原有的位置直接加就行,即进行下一步判断
// 条件三 !(uncontended = a.cas(v = a.value, v + x))
// 若为true 说明 uncontended为false,说明当前线程对应的cell有竞争
// 若为false 说明uncontender为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)
1. 条件一 为true,即我的cells数组未初始化,但是看第一层if你会发现,之所以会走到这里的代码,还有一个潜在的条件,多个线程写base发生了竞争
2. 条件二 为true cells已经被创建好了,当前线程对应的小标cell为空,我要此方法帮我去写数据。
3. 条件三 为true cas失败,当前线程对应的cell有竞争
*/
longAccumulate(x, null, uncontended);
}
}
// longAccumulate()方法
/*
/* 哪些情况会调用 longAccumulate(x, null, uncontended)
1. 条件一 为true,即我的第cells数组未初始化,但是看一层if你会发现,之所以会走到这里的代码,还有一个潜在的条件,多个线程写base发生了竞争
2. 条件二 为true cells已经被创建好了,当前线程对应的小标cell为空,我要此方法帮我去写数据。
3. 条件三 为true cas失败,当前线程对应的cell有竞争
*/
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
// wasUncontended 只有cells初始化之后,并且当前线程竞争修改失败 时,这个变量才会为false
int h;
// 若当前线程未设置哈希值,则进行以下操纵
if ((h = getProbe()) == 0) {
// 设置哈希值
ThreadLocalRandom.current(); // force initialization
// 取出哈希值
h = getProbe();
// 为什么? 因为默认情况下,当前线程 肯定是写入到了 cell[0] 位置,不把它当作一次真正的竞 争
wasUncontended = true;
}
// collide 表示扩容意向 true表示可能会扩容 false一定不扩容
boolean collide = false; // True if last slot nonempty
for (;;) {
/*
*/
Striped64.Cell[] as; Striped64.Cell a; int n; long v;
// CASE1
// 表示 cells 已经初始化,当前线程应该把数据写入到对应的cell中
// 程序走到这里的隐含条件 (二者之一)
// (a = as[getProbe() & m]) == null为true cells已经被创建好了,当前线程对应的小标cell为空,我要此方法帮我去写数据。
// !(uncontended = a.cas(v = a.value, v + x)) 为true cas失败,当前线程对应的cell有竞争
if ((as = cells) != null && (n = as.length) > 0) {
// CASE 1.1 当前线程对应的下标为null ,需要创建 新的cell
if ((a = as[(n - 1) & h]) == null) {
// cellBusy==0 说明无锁,可以进行新建
if (cellsBusy == 0) { // Try to attach new Cell
// 创建new cell
Striped64.Cell r = new Striped64.Cell(x); // Optimistically create
//
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Striped64.Cell[] rs; int m, j;
/*
条件一 :(rs = cells) != null &&(m = rs.length) > 0 恒成立
条件二 :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
}
}
collide = false;
}
// CASE 1.2 初始化之后,并且当前线程竞争修改失败 时,这个变量才会为false。
// 当为false后,去找 重置哈希值的方法,目的是去别的cell中去看一下,最好就是重置哈希之后走到了CASE1.1,新建一个cell
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// CASE 1.3
// 当前线程rehash过哈希值,新命中的cell不为空,来到这里
// 如果写成功后,退出自旋。失败了表示竞争失败。
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
// CASE1.4
// 条件一:cells数组的长度 >=NCPU 表示不扩容了
// 条件二:true 其它线程已经扩容过cells数组,当前线程继续去rehash自旋
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
// CASE1.5
// cells 数组可能需要扩容
else if (!collide)
collide = true;
// CASE 1.6 进行扩容的代码
//
// (一个很有的事情:当一个线程想要添加数据,当走到这里时,其实已经失败了2次,LongAdder认为是数组太小了,所以进行扩容)
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
Striped64.Cell[] rs = new Striped64.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
}
// 重置当前线程的哈希值,对应的cell也会变。然后进行自旋
h = advanceProbe(h);
}
// CASE2
// cell 未初始化来到的位置,此时as==null
/*
cellsBusy == 0 当前为加锁
cells == as 由于多线程的场景,当某一个线程从CASE1过来的时候,有可能刚走,然后另一个线程就初始化数组完成了,避免多次初始化
casCellsBusy() true 表示获取锁成功,初始化的时候加锁
*/
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
// 还是为了避免多线程 的场景导致重复初始化的原因,可能会丢失数据
if (cells == as) {
Striped64.Cell[] rs = new Striped64.Cell[2];
rs[h & 1] = new Striped64.Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
/*
CASE3
当前cellsBusy加锁状态,cells数组被其它线程正在 扩容/初始化 ,所以将数据加到base中
*/
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
}