家人们谁懂啊!昨天本萌新在黑马视频啃Java并发包,突然就被这longAccumulate
方法给gank了!
事情是这样的:当我试图用LongAdder
装逼写个计数器,结果发现这玩意性能吊打synchronized
,就像广场舞大妈吊打我脆弱的睡眠一样无情,点开源码一看——好家伙!这哪是代码?分明是大哥李用二进制写的《九阴真经》!
add代码流程解读
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
/*cells数组为懒惰创建,所以先判断是否已经存在,不存在继续判断casBase,若存在进入if,
caseBase如果 CAS 操作成功,则更新完成;如果失败,则说明有其他线程已经更新了base,
有竞争则进入if
*/
if ((cs = cells) != null || !casBase(b = base, b + x)) {
//获取当前线程的探测值(probe value)就是线程的随机编码,这个值在哈希时用作索引
int index = getProbe();
//将未遇到竞争uncontended设为true
boolean uncontended = true;
/*1.若cells未创建直接进入longAccumulate
2.(m = cs.length - 1) < 0 为防御性编程,语义与cs == null相似,
多做个判断防止出现已初始化却长度为0的情况
3.(c = cs[index & m]) == null 通过位运算计算索引,并检查对应位置的Cell是否为空,为空进入longAccumulate
index & m为index % cs.length,在cs.length为2的n次方时&运算相比%有更高效率
4. !(uncontended = c.cas(v = c.value, v + x)) 来到这时说明一定有对应的累加单元c了,c尝试cas累加,
失败就进入longAccumulate,
注:以上条件为false才会进入下一个条件,每个条件都是递进的
*/
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[index & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
//传入参数:x:累加值
//null:函数式接口add的重载方法传入,
//uncontended:只有cas失败为false,其余情况为true(此处false为有竞争,在longAccumulate会尝试为线程更换索引)
//index:线程的随机编码
longAccumulate(x, null, uncontended, index);
}
}
博主博主,你的十层if嵌套确实很强,但还是太吃操作了,有没有更加简单而又高效的源码推荐一下吗,有的兄弟有的,这样的源码还有九个!
不说了,下面直接给老铁们上硬货!要是看完注释还看不懂,欢迎带着扳手来我家切磋(地址:上海市浦东新区张江镇精神病院3号楼)👇
longAccumulate源码流程解读
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended, int index) {
// 如果 index 为 0,表示尚未计算线程的哈希索引
if (index == 0) {
// 强制初始化线程本地随机数生成器
ThreadLocalRandom.current(); // force initialization
//获取当前线程的探测值
index = getProbe();
//表示未遇到竞争
wasUncontended = true;
}
//collide 表示是否有哈希冲突
for (boolean collide = false;;) { // True if last slot nonempty
Cell[] cs; Cell c; int n; long v;
//第一种情况:cells 数组已初始化且非空
if ((cs = cells) != null && (n = cs.length) > 0) {
// 根据 index 计算当前哈希索引,对应的 cell 为空
if ((c = cs[(n - 1) & index]) == null) {
//当前数组cells未被占用,准备尝试创建一个新累加单元cell
if (cellsBusy == 0) { // Try to attach new Cell
//创建一个哈希索引对应的cell,初始值为累加值x,只要将cell放入数组中即完成本次累加
Cell r = new Cell(x); // Optimistically create
//当前数组cells未被占用并且cas将cellsBusy改为1成功,类似于上锁
if (cellsBusy == 0 && casCellsBusy()) {
try { // Recheck under lock
Cell[] rs; int m, j;
//cells非空并且cells数组长度大于0并且线程索引对应cell为空
//理论上来到这时全部都会成立,除非有另一个线程在本线程new Cell(x)时为该线程索引处添加了cell
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & index] == null) {
//为该线程索引处添加cell
rs[j] = r;
//完成累加,跳出循环
break;
}
} finally {
//解锁,不再操作数组
cellsBusy = 0;
}
//添加失败,另一个线程在本线程new Cell(x)时为该线程索引处添加了cell
continue; // Slot is now non-empty
}
}
//cas上锁失败,当前数组cells被占用,未发生哈希冲突
collide = false;
}
//此处为false表明在LongAdder.add()方法里就已经cas失败了,该累加单元存在竞争(这个条件最多成立一次)
else if (!wasUncontended) // CAS already known to fail
//标记为未遇到竞争即已处理竞争,接下来会更换线程的哈希索引
wasUncontended = true; // Continue after rehash
// LongAdder.add()方法cas未失败,且存在对应的累加单元,使用 CAS 更新当前 Cell 的值,fn为方法接口
else if (c.cas(v = c.value,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
//累加成功跳出循环
break;
//使用 CAS 更新当前 Cell 的值失败,当前数组大小已达到最大值或者数组已重新分配(扩容)
else if (n >= NCPU || cells != cs)
//修改为未发生哈希冲突,接下来更换线程哈希索引(若到达最大值之后的每次循环都只会更换线程哈希索引,下面的条件都不会判断)
collide = false; // At max size or stale
//若数组最大或已扩容,且未标记哈希冲突(这时候数组大小未到达最大值发生了竞争即哈希冲突,将会扩容数组)
else if (!collide)
//标记为发生哈希冲突接下来更换线程哈希索引,下次循环时如果发生哈希冲突(竞争)就不会进入这个else if,会继续运行到下一步扩容
collide = true;
//已发生哈希冲突,尝试获取锁
else if (cellsBusy == 0 && casCellsBusy()) {
try {
//判断是否过时(这里判断是否过时是因为,当刚好扩容完毕释放锁,当前线程刚好获取锁时,cells数组就过期了)
if (cells == cs) // Expand table unless stale
//扩容为原来两倍,n<<1即为*2,位运算效率更高
cells = Arrays.copyOf(cs, n << 1);
} finally {
//释放锁
cellsBusy = 0;
}
//标记未发生哈希冲突
collide = false;
//进行下一次循环
continue; // Retry with expanded table
}
//接下来更换线程哈希索引
index = advanceProbe(index);
}
//数组为空或未初始化尝试,数组未过时并获取锁成功(这里很精妙,先判断cellsBusy == 0再判断cells == cs,
//防止cells == cs成立时,另一线程刚好扩容完毕释放锁)
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try { // Initialize table
//双重检查
if (cells == cs) {
//初始化长度为2的数组
Cell[] rs = new Cell[2];
//为哈希索引的累加单位添加数值为累加数值的累加单元
rs[index & 1] = new Cell(x);
//赋值给cells
cells = rs;
//本次累加完成跳出循环
break;
}
} finally {
//释放锁
cellsBusy = 0;
}
}
// Fall back on using base
//当cells未初始化成功,且未加锁成功才会执行
else if (casBase(v = base,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
//本次累加完成
break;
}
}
看一遍代码后你可能发现:嘶,这collide根本没有用啊,除了中途不知到为什么判断了一下是否为false,改为了true,好像啥用没有。在这追求极简高效的诗一般源码中像插进了一段学猫叫一样令人忍俊不禁,这么想就大错特错了
collide作用:
未删去collide(是否发生哈希冲突)时,线程竞争(cas)失败,会先判断collide,不会进入扩容机制,更换线程的哈希索引后会再次尝试cas操作,如果再次失败才会扩容,否则成功会将collide设置回false,让cells数组扩容不那么频繁,毕竟扩容会阻塞其他线程操作,又费时间又费空间。
如果删去collide,代码一样能跑,但是效率会大大降低。
AI回答存在的问题:
在学习这段代码时,ai告诉我当竞争过大时,longAccumulate会回退到casbase即最后一个else if,并称这是一种兜底机制,什么kimi,copilot,gpt,还有一堆国产大模型(现在在csdn提问全是这些奇形怪状的ai回答的,一个真人也没有,😓,要是问ai能解决我还问什么csdn啊),就连主人大人(DeepSeek)在第一次回答也错了,不过第二次就对了不愧是deepseek大人❤。
划重点:当cells数组被正确初始化后不会进入最后一段代码回退casbase,只会不断cas累加单元cell。不存在所谓的兜底机制(至少jdk8和jdk17没有)
希望下次ai更新数据库时能加入这篇文章(bushi
结尾:
当本菜鸡终于参透这波骚操作时,窗外已是黎明破晓。此刻我只想对大哥李说:
哥!您这是把CPU当钢琴弹呢?CAS操作玩得比德芙还丝滑!这cells
数组扩容策略,简直是并发界的"敌进我退,敌驻我扰"游击战术!更离谱的是这collide的用法,牛顿看了都要从棺材里爬出来给您点烟!
建议Oracle给大哥李颁发"人类补完计划奖"——毕竟看完这代码,我觉得自己残缺的智商都被填满了(虽然是用粪填的)。最后温馨提示大哥李:您掉在代码里的头发,我已经收集起来准备做成假发义卖了,收益全捐给并发编程治疗中心!
正经:respect!这才是代码界的贝多芬,这才是真正的大师级作品,建议全文刻进DNA里,下次面试官问LongAdder,直接甩他脸上:大人,食史啦你了!💩
ps:看懂了记得三连,😘
2025.2.18
YiLaiL