前言
适合阅读者:看了源码但没看懂,看得头大的
本文参考源码:JDK8版本
本文作用:
1.深入理解LongAdder原理
2.辅助源码阅读
阅读建议:配合JDK8源码阅读
总体过程就是这样
详细分析
1.类的结构是怎样的
继承了Striped64抽象类,这个抽象类中有cells数组,base变量,有longAccumulate方法
LongAdder里自己定义了add方法和sum方法.
2.调用add(x)方法时,发生了什么
第一步.先判断cells有没有被初始化
1.没有过初始化------代表此时没有竞争发生过,CAS累加base变量
1.1 累加成功-----add方法执行完毕
1.2 累加失败----此时进入第二步
2.cells有过初始化-----代表此时发生过竞争,进入第二步
第二步.当1.cells数组有过初始化, 2.当前线程需要操作的cells数组中的那个位置已经创建了一个cells对象 3.CAS累加数据成功,则弹出add方法
这三个条件有任意一个未满足,调用longAccumulate方法
3.调用longAccumulate方法,发生了什么
调用这个方法的原因—解决上述三个情况任意一种
根据进入longAccumulate方法的三种情况,可以知道longAccumulate就是为了解决这三个情况下如何实现累加操作而诞生的
1.处理 Cells 数组未初始化的情况
- 检查cellsBusy(锁标记),如果为0(表示无锁),尝试加锁(置1),加锁成功进入下一步
- 初始化长度为2的cells数组
- 根据add(x)时传入的x,创建一个值为x的cell对象,根据当前线程的hash值&1得出存放到cells数组的位置,放入
- 释放cellBusy(置0).
- 结束方法
如果获取cellsBusy失败的话-----失败回退:如果其他线程正在初始化(cellsBusy 被占用),则尝试直接通过 CAS 累加到 base 变量(兜底方案)。
2.处理当前线程在cells数组中需要操作的位置未被占用(该位置为 null)的情况
- cellsBusy是否为0,为0进入下一步
- 创建新 Cell:生成一个值为 x 的新 Cell 对象。
- 再次检查 cellsBusy 锁,确保无竞争后,CAS 加锁。
- 二次检查:加锁后再次确认该位置仍为 null(避免其他线程已修改)。
- 插入 Cell:将新 Cell 放入数组对应位置,释放锁(置0)。
- 结束方法.
如果获取cellBusy锁失败-----失败重试:如果插入过程中被其他线程抢占(如该位置已被填充),则回到循环重试。
3.处理 当前线程在cells数组中需要操作的位置存在竞争(CAS 操作失败)的情况
1.把wasUncontended设置为true,为的是调用一次advanceProbe(h)重新生成当前线程hash值,换一下当前线程需要操作的位置
2.尝试两次CAS**(每一次都重新生成当前线程hash值,使用新的所要累加的位置,)**,累加当前位置上的值(当前值加x),
2.1 如果有一次CAS成功,结束方法(退出这个情况的唯一可能)
2.2 如果两次都失败,进入判断是否进入需要扩容,需要就扩容.不需要就不断重试
3.数组是否需要扩容
3.1 当数组长度小于CPU核数,扩容:通过 CAS 获取 cellsBusy 锁(置1),将数组大小翻倍,复制旧数据到新数组,释放锁(置0)。重新执行操作2.
3.2 当数组长度大于等于CPU核数,不再扩容,调用advanceProbe(h)重新生成当前线程需要累加的位置,不断重试操作2,直到成功
我写的这个步骤其实有错误,这些步骤里的返回到操作2是错误的,实际上返回到的是第二个情况(判断当前线程在cells数组中需要操作的位置未被占用(该位置为 null))那里,这里为了理解起来方便些,描述为返回到操作2