longAdder
表示一个值 而已
Striped64
package java.util.concurrent.atomic;
import java.util.function.LongBinaryOperator;
import java.util.function.DoubleBinaryOperator;
import java.util.concurrent.ThreadLocalRandom;
@SuppressWarnings("serial")
abstract class Striped64 extends Number {
@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);
}
}
}
// 表示当前计算机CPU数量,什么用? 控制cells数组长度的一个关键条件
static final int NCPU = Runtime.getRuntime().availableProcessors();
transient volatile Cell[] cells;
// 没有发生过竞争时,数据会累加到base上|当cells扩容时,需要将数据写到base中
transient volatile long base;
// 初始化cells或者扩容cells都需要获取锁,0表示无锁状态,1表示其他线程已经持有锁了
transient volatile int cellsBusy;
Striped64() {
}
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
// 通过 CAS 获取锁
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
// 获取当前线程的 hash 值
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
// 重置当前线程的 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;
}
// 都有哪些情况会调用?
// 1.true->说明cells未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
// 2.true->说明当前线程对应下标的cell为空,香要创建longAccumulate 支持
// 3.true->表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
// wasUncontended:只有cells初始化之后,并且当前线程竞争修改失败,才会是false
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
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) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
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;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
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; // Retry with expanded table
}
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[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))))
break; // Fall back on using base
}
}
final void doubleAccumulate(double x, DoubleBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
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) { // Try to attach new Cell
Cell r = new Cell(Double.doubleToRawLongBits(x));
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
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;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value,
((fn == null) ?
Double.doubleToRawLongBits
(Double.longBitsToDouble(v) + x) :
Double.doubleToRawLongBits
(fn.applyAsDouble
(Double.longBitsToDouble(v), x)))))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
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; // Retry with expanded table
}
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(Double.doubleToRawLongBits(x));
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
else if (casBase(v = base,
((fn == null) ?
Double.doubleToRawLongBits
(Double.longBitsToDouble(v) + x) :
Double.doubleToRawLongBits
(fn.applyAsDouble
(Double.longBitsToDouble(v), x)))))
break; // Fall back on using base
}
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long BASE;
private static final long CELLSBUSY;
private static final long PROBE;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> sk = Striped64.class;
BASE = UNSAFE.objectFieldOffset
(sk.getDeclaredField("base"));
CELLSBUSY = UNSAFE.objectFieldOffset
(sk.getDeclaredField("cellsBusy"));
Class<?> tk = Thread.class;
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
} catch (Exception e) {
throw new Error(e);
}
}
}
//表示当前计算机CPU数量,什么用?控制cells数组长度的一个关键条件
static final int NCPU = Runtime.getRuntime().availableProcessors();
// 没有发生过竞争时,数据会累加到 base上 |当cells扩容时,需要将数据写到base中
transient volatile long base;
// 初始化cells或者扩容cells都需要栋取锁、0表示无锁状态,1表示其他线程已经持有锁了
transient volatile int cellsBusy;
add方法
public void add(long x) {
// as 表示cells引用
// b 表示获取的base值
// v 表示期望值
// m 表示 cells数组的长度
// a 表示当前线程命中的cell单元格
Cell[] as; long b, v; int m; Cell a;
// 条件一: true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
// false->表示cells未初始化,当前所有线程应该将数据写到base中
// 条件二: false->表示当前线程cas替换数据成功,
// true ->表示发生竞争了,可能需要重试或者扩容
if ((as = cells) != null || !casBase(b = base, b + x)) {
// 什么时候进来
// 1. true->表示 cells 已经初始化过了,当前线程应该将数据写入到对应的cell中
// 2. true->表示发生竞争了,可能需要重试或者扩容
// true -> 未竞争 false-> 发生竞争
boolean uncontended = true;
// 多线程的情况会导致 出现空 地址一样
// 条件1:true ->说明 cells 未初始化,也就是多线程写 base 发生竞争了
// false ->说明 cells 已经初始化了,当前线程应该是找自己的 cell 写值
//
// 条件2:getProbe()获取当前线程的hash值 m表示cells长度-1 cells长度一定是2的次方数―15= b1111
// 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有竞争[重试|扩容]
// 调用Striped64中的方法处理
longAccumulate(x, null, uncontended);
}
}
longAccumulate方法
// 不出意外到 CASE 2
// 判断有没有锁 没拿到锁 或者其他线程初始化掉了 把值加到base BASE 3
// 1.true -> 说明cells未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
// CASE 1 再到 1.1 创建 CEll(X) 成功的话 拿到锁 其他线程没有设置过 XX
// 2.true -> 说明当前线程对应下标的cell为空,香要创建longAccumulate 支持
// 先 CASE 1.2 修改发生线程的 hash值 CASE 1.3 成功退出 没成功 设置扩容意向 改hash 到 1.5 第二次扩容失败 到 1.6 扩容
// 3.true -> 表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
// 存储线程的probe值 可以理解为 hash 值
int h;
// 如果getProbe()方法返回0,说明随机数未初始化
if ((h = getProbe()) == 0) {
// 强制初始化
ThreadLocalRandom.current(); // force initialization
// 重新获取probe值
h = getProbe();
// 都未初始化,肯定还不存在竞争激烈
// 为什么?因为默认情况下当前线程肯定是写瓜到了 cells[0]位置。不把它当做一次真正的竞争
wasUncontended = 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;
// cells已经初始化过
// 初始化后 某一个未空
// CASE1:表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
if ((as = cells) != null && (n = as.length) > 0) {
// 当前线程所在的Cell未初始化
// 2.true->说明当前线程对应下标的cell为空,需要创建longAccumulate 支持
// 3.true->表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
// CASE 1.1 true -> 表示当前线程对应的下标位置的 cell 为 null ,需要创建new Cell
if ((a = as[(n - 1) & h]) == null) {
// true->表示当前锁未被占用 false->表示锁被占用
// 当前无其它线程在创建或扩容cells,也没有线程在创建Cell
if (cellsBusy == 0) { // Try to attach new Cell
// 拿当前的x创建Cell
// 新建一个Cell,值为当前需要增加的值
Cell r = new Cell(x); // Optimistically create
// 条件一: true->表示当前锁未被占用false->表示锁被占用
// 条件二:true->表示当前线程获取锁成功false->当前线程获取锁失败..
// 再次检测cellsBusy,并尝试更新它为1
// 相当于当前线程加锁
if (cellsBusy == 0 && casCellsBusy()) {
// 是否创建成功标记
// 是否创建成功
boolean created = false;
try { // Recheck under lock
// rs 当前 cells 引用
// m cells 长度
// j 当前线程命中的下标
Cell[] rs; int m, j;
// 多线程
// 条件一 条件二恒成立
// rs[j = (m - 1) & h] == null为了防止其它线程初始化过该位置,然后当前线程再次初始化该位置
// 导致丢失数据
// 重新获取cells,并找到当前线程hash到cells数组中的位置
// 这里一定要重新获取cells,因为as并不在锁定范围内
// 有可能已经扩容了,这里要重新获取
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 把上面新建的Cell放在cells的j位置处
rs[j] = r;
// 创建成功
created = true; //===
}
} finally {
// 相当于释放锁
cellsBusy = 0;
}
// 创建成功了就返回
// 值已经放在新建的Cell里面了
if (created)
break;
continue; // Slot is now non-empty
}
}
// 扩容意向强制改为了false
// 标记当前未出现冲突
collide = false;
}
// 当前线程所在的Cell不为空,且更新失败了
// 这里简单地设为true,相当于简单地自旋一次
// 通过下面的语句修改线程的probe再重新尝试
// CASE1.2:
// wasUncontended:只有cells初始化之后,并且当前线程竞争修改失败,才会是false
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// CASE1.3
// true ->写成功,退出循环 到 LongAdder
// false ->表示rehash之后命中的新的cell 也有竞争 重试1次 再重试 1 次 到 CASE 1.6
// 再次尝试CAS更新当前线程所在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之后重试即可
// CASE 1.5:
// !collide = true设置扩容意向为true但是不一定真的发生扩容
// 数组不能超过数组的数量
// 如果cells数组的长度达到了CPU核心数,或者cells扩容了
// 设置collide为false并通过下面的语句修改线程的probe再重新尝试
else if (n >= NCPU || cells != as)
// 扩容意向改为false,表示不扩容了
collide = false; // At max size or stale
else if (!collide)
collide = true;
// CASE 1.6 正真扩容的
// 条件一: cellsBusy == 0 true->表示当前无锁状态,当前线程可以去竞争这把锁
// 条件二: cascellsBusy true->表示当前线程获取锁成功,可以执行扩容逻辑
// false->表示当前时刻有其它线程在做扩容相关的操作。|
// casCellsBusy 竞争这把锁
// 上上个elseif都更新失败了,且上个条件不成立,说明出现冲突了
// 明确出现冲突了,尝试占有锁,并扩容
else if (cellsBusy == 0 && casCellsBusy()) {
try {
// 线程 竞争 防止 重复扩容
// cells == as
// 检查是否有其它线程已经扩容过了
if (cells == as) { // Expand table unless stale
// 新数组为原数组的两倍
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
}
// 更新失败或者达到了CPU核心数,重新生成probe,并重试
h = advanceProbe(h); // 重置 当前线程 hash 值
}
// 未初始化过cells数组,尝试占有锁并初始化cells数组
//CASE2:前置条件cells还未初始化as 为null
//条件一: true 表示当前未加锁
//条件二: cells == as?因为其它线程可能会在你给as赋值之后修改了cells
//条件三:true表示获取锁成功会把cellsBusy = 1, false 表示其它线程正在持有这把锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
// 是否初始化成功
boolean init = false;
try { // Initialize table
// 检测是否有其它线程初始化过
// cells == as?防止其它线程已经初始化了,当前线程再次初始化导致丢失数据
if (cells == as) {
// 新建一个大小为2的Cell数组
Cell[] rs = new Cell[2];
// 找到当前线程hash到数组中的位置并创建其对应的Cell
rs[h & 1] = new Cell(x);
// 赋值给cells数组
cells = rs;
// 初始化成功
init = true;
}
} finally {
// 释放锁
cellsBusy = 0;
}
// 初始化成功直接返回
// 因为增加的值已经同时创建到Cell中了
if (init)
break;
}
// 如果有其它线程在初始化cells数组中,就尝试更新base
// 如果成功了就返回
// CASE3:
// 1.当前 cellsBusy 加锁状态,表示其它线程正在初始化 cells,所以当前线程将值累加到basel
// 2.cells 被其它线程初始化后,当前线程需要将数据累加到 base
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
sum方法
// sum 方法不是准确的 高精度要用 atomicLong
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;
}
参考