前沿内容——伪共享问题
线程之间不必要的数据共享就称之为“伪共享”;由于实现了volatie关键字,因此线程之间会实时去内存读取和修改数据,而这一系列都是对缓存行的操作,那么缓存行是64字节单位的,因此可能比如两个不关联的int都会同时被重新加载和更新,因此必须将一行数据进行填充让两个数据不在同一个缓存行以实现减少这种多个内存数据操作,一般直接在字段对竞争激烈的字段加注解即可;
public class Point {
int x;
@Contended
int y;
}
这种就是创建的时候,自动填充满64字节占满一个缓存行
@sun.misc.Contended static final class Cell {
}
LongAdder原理——初始化
/*
首先直接base失败则进入这里。然后利用hash获取一个cell元素
直接对这个value进行累加再失败就进入full模式
*/
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = 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);
}
}
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;
/**
* @(as = cells) != null 表示已经初始化过了
*/
if ((as = cells) != null && (n = as.length) > 0) {
……
}
/**
* @初始化cell
* 此处需要cellsBusy上锁,避免两个线程同时初始化,失败的线程则直接casBase
*/
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;
}
/**
* @初始化失败则再一次更新base数字
* 再失败就继续轮回,但此时已经cell已经初始化了
*/
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
LongAdder原理——cell操作详情
for (;;) {
/**
* as:整个cell数组的指针
* a:每次哈希探针定位到元素
*/
Cell[] as; Cell a; int n; long v;
/**
@(1)第一次尝试,直接将新数字V生成一个cell
* @(as = cells) != null 表示已经初始化过了
*/
if ((as = cells) != null && (n = as.length) > 0) {
/**
* @a:当前线程利用探针定位到一个cell元素
* @n:cell数组长度
* @as:cell整个数组
*/
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) {
/**
* 将要增加的数字变成一个cell
* 然后cellsBusy上自旋锁
*/
Cell r = new Cell(x);
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try {
/**
* 将要新增的cell加入到cell数组,并且要保证这个下标null值就是不会冲突,下次就会改变探针
* 继续试探是不是null
* rs:cell整个数组
*/
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)//创建成功任务就结束了,利用sum可以同时统计cell和base的值
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
/**
* @wasUncontended用于协调探针需要换一个
*如果是fasle则直接进入下一次循环,则不继续rehash了;wasUncontended代表了有没用哈希参与过cas
*/
else if (!wasUncontended)
wasUncontended = true;
/**
* @(2)第二次尝试,将上面a就是哈希选中的一个元素,将新值直接累加进去
* 因为哈希已经均摊,这种cas累加很明显竞争会小问题
*/
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
else if (n >= NCPU || cells != as)
collide = false;
else if (!collide)
collide = true;
/**
* 仍然没有成功则resize,增大cell的容量,减少竞争一般就是两倍
*/
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);
}
LongAdder原理——返回
由于LongAdder并不会将cell中的数字累加到base中所以cell是滞留的,sum的时候要这部分考虑上;cell显然典型的空间换时间的做法
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;
}
LongAdder原理——总结
cellsBusy:用于协调自旋过程中一些可能产生线程安全的操作,比如cell初始化、cell元素置换和rehash等等;
升级过程:
外部会caseBase一次,然后会用探针找到一个元素对其进行累加,如果再次失败就进入full模式;
full模式涵盖了cell的初始化和扩容,每次扩容是2倍;
内部也是一样,内部第一次会直接将自己的数字生成为cell,如果创建失败就hash定位到元素累加,如此循环往复,每次线程的探针都会发生变化以此提高随机程度;