解决伪共享

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)
L13-4cycle
L210-20cycle
L340-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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值