LongAddr
LongAddr这个类是jdk1.8才开始有的,这个类的主要作用就是原子递增或者递减,jdk8之前有AtomicLong这个类可以用来计数,那为什么还要有LongAddr呢?我们都知道,AtomicLong是用volatile和cas来保证原子性,当并发特别高时大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的cas操作会成功,这就造成了其他线程竞争失败后,会通过自旋的方式再次尝试,白白浪费CPU资源。
LongAddr数据类型
LongAddr继承自java.util.concurrent.atomic包下的Striped64抽象类,Striped64类维护了三个全局变量。
Cell类型的数组,long类型的base,int类型的cellsBusy。
我们来看看Cell的数据结构:
@sun.misc.Contended
是用来解决伪共享,进行字节填充的。Cell的数据结构和AtomicLong差不多。
LongAddr是怎么解决AtomicLong的问题的呢?
大量线程写LongAddr时会在不同的Cell上累加,读取时会将base和Cell数组中每个Cell的值求和返回。
add方法
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {//(1)
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||//(2)
(a = as[getProbe() & m]) == null ||//(3)
!(uncontended = a.cas(v = a.value, v + x)))//(4)
longAccumulate(x, null, uncontended);//(5)
}
}
- 当没有产生并发时,会在base变量上进行cas
- 如果产生并发,会在Cell数组中的Cell上进行cas
longAccumulate方法
/**
*进入到这个方法里面有三种情况
*1. cells数组没有初始化,base更新失败
*2.cells数组初始化,但是要更新的位置的cell没有元素
*3.cells数组初始化,要更新的位置cell元素存在,但是更新失败
*
*/
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
//强行初始化,初始化当前线程的probe变量赋值给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) {
//当前cells数组已经初始化,但是要更改的位置的cell没有元素,
//对应上述条件2
if ((a = as[(n - 1) & h]) == null) {
//如果现在没有其他线程在修改或者创建cells,
//或者其他线程在创建Cell
//尝试创建一个Cell,值为x,放入计算出来的位置,如果成功,返回
//如果失败,自旋重试
//如果有其他线程正在操作,重试
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;
}
//如果cells初始化,并且要更改的位置的cell元素存在,
//先尝试在此位置的cell上操作,如果失败。
//判断cells的元素数量是不是大于CPU数量或者是不是已经扩容了
//如果两个条件都不满足,则标识已经发生了冲突,重新hash位置
//新位置上如果也有元素,并且更新失败,这时就需要扩容了,进行两倍扩容
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);
}
//如果cells未初始化,base更新失败,对应上述条件1
// 如果此时没有其他线程在创建或者修改cells,或者在创建Cell
// 那么创建cells,并且hash出一个位置,将新建的Cell放入此位置
//成功就返回
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;
}
//如果当时有其他线程在创建或者修改cells,或者创建cell,
//那么尝试修改base值,成功的话返回,失败自旋。
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
longAccumulate方法比较复杂,总结下来这个方法就是在Cell数组上进行并发修改,未初始化时初始化Cell数组,产生冲突时扩容Cell数组。其中cellsBusy变量就充当一个锁的作用,保证只有一个线程在初始化Cell数组或者扩容Cell数组或者创建Cell。
上面代码注释上所写的,进入longAccumulate方法有三个条件。
- cells数组没有初始化,base更新失败
-
- 如果有其他线程正在操作,那么再次尝试casbase变量,成功返回,失败自旋
- 如果没有其他线程正在操作,那么创建Cell数组,初始容量为2,并hash一个位置进行cas
- cells数组初始化,但是要更新的位置的cell没有元素
-
- 如果有其他线程正在操作,那么重试
- 如果没有其他线程正在操作,创建一个Cell,放入hash的位置,成功返回,失败自旋
- cells数组初始化,要更新的位置cell元素存在,但是更新失败
-
- 先尝试在此位置的cell上操作,如果失败。先尝试在此位置的cell上操作,如果失败。判断cells的元素数量是不是大于CPU数量或者是不是已经扩容了,如果两个条件都不满足,则标识已经发生了冲突,重新hash位置,新位置上如果也有元素,并且更新失败,这时就需要扩容了,进行两倍扩容
sum方法
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;
}
就是Cell数组中每个Cell的值加上base返回。