文章目录
1.原理之伪共享
其中Cell 即为累加单元类
//防止缓存行伪共享
/**
一个缓存行加入了多个Cell对象叫做伪共享
*/
//防止缓存行存入多个Cell对象.
@sun.misc.Contended
static final class Cell{
volatile long value;
Cell(long x) {value =x;}
//最重要的方法,用来cas 方式进行累加, prev 表示旧值,next表示新值
final boolean cas(long prev,long next){
return UNSAFE.compareAndSwapLong(this,valueOffset,prev,next);
}
//省略不重要代码
}
得从缓存说起
缓存与内存的速度比较
从cpu到 | 大约需要的时钟周期 |
---|---|
寄存器 | 1 cycle (4GHZ的CPU约为0.25ns) |
L1 | 3-4cycle |
– | – |
L2 | 10-20cycle |
L3 | 40-45cycle |
内存 | 120-240cycle |
因为CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64byte(8个long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中.
CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的真个缓存行必须失效.
因为Cell 是数组形式,在内存中是连续存储的,一个Cell 为24字节(16字节的对象头(8个字节的markword+8个字节的类型指针), 8字节的value)因此缓存行可以存下2个的Cell对象.这样问题来了:
- Core-0 要修改 Cell[0]
- Core-1 要修改Cell[1]
无论谁成功,都会导致对方Core的缓存行失效,比如Core-0中
Cell[0] =6000,Cell[1]=8000要累加Cell[0]=6001,Cell[1] = 8000,这时会让Core-1 的缓存行失效.
思考:想个办法让Cell[0] 和Cell[1]处在不同的缓存行?
@sun.misc.Contended用来解决这个问题,它的原理是在使用次注解的对象或字段的前后各增加128字节大小的padding,从而让CPU将对象预读至缓存占用不同的缓存行,这样,不会造成对方缓存行的失效。
2.原子累加器LongAdder increment()方法解读
public void increment() {
add(1L);
}
//第一次
public void add(long x) {
//累加单元 as
Cell[] as;
//b为(Striped64)基础值,如果没有竞争,则用cas累加这个域
//v为当前线程的cell的value值
long b, v;
// //m未知
int m;
//a为当前线程的cell
Cell a;
//判断累加单元的数组是否为空(cell是懒惰创建的没有竞争时为null)
//第一次为空 然后判断 对基础的累加值进行累加(cas操作)(如果失败进入if代码块里面)
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
//第一次as==null
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
//第一次直接执行longAccumulate
//具体会做cells的创建,包括cell的创建等等
longAccumulate(x, null, uncontended);
}
}
//第二次
public void add(long x) {
//累加单元类的数组.
Cell[] as;
//b为(Striped64)基础值,如果没有竞争,则用cas累加这个域
//v为当前线程的cell的value值
long b, v;
//m未知 a为当前线程的cell
int m;
//当前线程的cell
Cell a;
/**判断累加单元的数组是否为空(cell是懒惰创建的没有竞争时为null)
* ||对基础的累加值进行累加
*
*
*/
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
//第二次as不为空说明发生了竞争
if (as == null || (m = as.length - 1) < 0 ||
//第二次,判断当前线程有没有对应的cell
(a = as[getProbe() & m]) == null ||
//给当前累加单元cells 进行cas操作.如果成功进入
!(uncontended = a.cas(v = a.value, v + x)))
//第一次进入longAccumulate
//具体会做cells的创建,包括cell的创建等等
longAccumulate(x, null, uncontended);
}
}
3.解读longAccumulate()方法.
情况1.cells数组还不存在的情况
//先研究cells数组还不存在的情况.
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {... }
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 然后加锁,如果加锁成功
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try {
// Initialize table
//看一下其他线程是否已经创建好了cells数组
if (cells == as) {
//创建了初始值为2的rs的累加数组.
Cell[] rs = new Cell[2];
//赋值给累加单元,给一个初始值1
//注意我们的cell数组是2但是我们只存进去一个
//cell这体现了懒惰初始化不到万不得已(用到的时候)
//就不会出现这种事.
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
//解锁开
cellsBusy = 0;
}
if (init)
//退出循环
break;
}
//加锁失败后对base进行累加,如果成功退出for失败就重新进入for
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
情况2:数组创建好了累加单元没有创建
for (; ; ) {
Cell[] as;
Cell a;
int n;
long v;
//不为空,其他线程把累加线程创建好了.
if ((as = cells) != null && (n = as.length) > 0) {
//如果当前线程的累加单元没有创建好.
//n-1 & h 就相当于 as h%(n-1)
//位运算是二进制运算。算数运算需要转化为位运算
if ((a = as[(n - 1) & h]) == null) {
//没有加锁也就是说其他线程没有进行操作
if (cellsBusy == 0) { // Try to attach new Cell
//新建一个cell
Cell r = new Cell(x); // Optimistically create
//再次判断没有加锁,(再次判断的原因是因为当你新建的时候可能加锁
//casCellsBusy()加锁成功后
//这里不检查不行因为cas交换前的数没办法办证没被修改
if (cellsBusy == 0 && casCellsBusy()) {
//初始created=false;
boolean created = false;
try { // Recheck under lock
//在锁定的状态下重新检查
Cell[] rs;
int m, j;
//如果其他线程没有对cells进行更改,
//并且当前线程的累加单元没有创建好
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
//存入cells数组中并且置创建状态位true
rs[j] = r;
created = true;
}
} finally {
//解锁
cellsBusy = 0;
}
if (created)
//初始化当前线程的cell后,退出循环
//否则
break;
continue; // Slot is now non-empty
}
}
collide = false;
情况3:cells数组也存在,累加单元也存在
//如果累加单元的值成功就break
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//累加失败后,数组长度是否大于CPU(多核)个数
else if (n >= NCPU || cells != as)
//设置collide=false
collide = false; // At max size or stale
else if (!collide)
//超过cpu上限就会设置collide=false
//作用是不进入else if (cellsBusy==0)里面去
collide = true;
//如果没有超过CPU的数量.扩容
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
//数组左移一位
Cell[] rs = new Cell[n << 1];
//把旧数组中的数copy到新数组中去
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
//解锁.
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//改变当前线程对应的累加单元,一旦发现CPU超过上限了.
//
h = advanceProbe(h);
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;
}