原子累加器:LongAdder原理(伪共享原理)

使用LongAdder进行累加比使用AtomicLong性能更好的原因是:
Long Adder在有竞争时,设置多个累加单元,Thread-0累加Cell[0]而Thread-1累加Cell[1]…最后将结果汇总,这样他们在累操作加时操作的是不同的Cell变量,因此减少了CAS重试失败,因此性能更高

LongAdder类有几个关键域:
//累加单元数组,懒惰初始化
transient volatile Cell[] cells;
//基础值(如果没有竞争,则用cas累加这个域)
transient volatile long base;
//在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;

// @sun.misc.Contended 注解防止缓存行伪共享
@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);
}

伪共享:一个缓存行里存入了多个对象
原因:cpu与内存的速度差异大,需要靠预读的数据到缓存中来提升效率,但是缓存是以缓存行为单位,每个缓存行对应着一块内存,一般是64byte(8个long),缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中。
cpu要保证数据的一致性,如果某个cpu核心更改了数组,其他cpu核心对应的整个缓存行都会失效。

因为cell是数组形式,在内存中是连续存储的,一个cell为24字节(16字节的对象头+8字节的long型的value),因此缓存行可以存下两个cell对象,这样就是导致:
core-0要修改cell[0],cell[1]要修改cell[1],无论谁修改成功,都会导致对方的缓存行失效,因此就会到内存中去取值,缓存就没用了

@sun.misc.Contended 就是为了避免这种情况,它的原理是使用此注解的对象或字段的前后各增加128字节大小的padding,从而让cpu将对象
预读至缓存时占用不同的缓存行,这样就不会造成对方的缓存行的失效了。

LongAdder原理:
在这里插入图片描述
LongAdder类中的increment()调用了add()方法:

在这里插入图片描述

关键的add()的源码:
在这里插入图片描述

这段源码设计非常精巧:
首先当前线程去判断Cell数组是否为null(这个数组是懒惰创建,一开始没有竞争时Cell数组为null,竞争发生时才会尝试创建Cell数组,从而创建里面的cells累加单元),如果为null,说明没有竞争,就用cas在没有竞争时使用的基础值上累加,如果cas成功,则加期望值加到基础值之上,这个操作成功则返回,如果cas失败,则会进入longAccululate()函数的流程——创建Cell数组

如果一开始的判断(Cell数组不为null—有竞争,已经创建了Cell数组),再进行下面的判断:
判断当前线程对应的cell是否存在,如果不存在,则会进入longAccululate()函数的流程——创建Cell数组
如果存在,则用cas在创建号的累加单元上累加,即cas成功就将期望值累加到累加单元上,返回
cas失败,则会进入longAccululate()函数的流程——创建Cell数组

在这里插入图片描述

longAccumulate源码:

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
    }
}

其中for(; 😉{}就是死循环:不断的尝试创建数组,创建累加单元等
情况二:Cell数组不为null,有其他线程将累加单元数组创建好了但是累加单元还未创建——>第一个if对应的逻辑图
在这里插入图片描述
for (;😉 {
Cell[] as; Cell a; int n; long v;

if ((as = cells) != null && (n = as.length) > 0) { 情况二://cell数组不为null,有其他线程将累加单元数组创建好了,但是累加单元还未创建(因为这个累加单元的创建也是懒惰的,需用用到时再创建),下面的逻辑是当前那个线程创建了当前线程的累加数组和累加单元
    if ((a = as[(n - 1) & h]) == null) {  //获取当前线程,判断其是否存在累加单元a,如果为null,说明还没有被创建累加单元
        if (cellsBusy == 0) {       // Try to attach new Cell
            Cell r = new Cell(x);   // Optimistically create   //创建了一个累加单元,并将期望值存入其中,但是还未把这个累加单元放入数组中,
            if (cellsBusy == 0 && casCellsBusy()) { //cellsBusy == 0表示没有上锁,因此尝试上锁,因为此时需要将新创建的累加单元设置到新创建的累加数组中空的位置上,因此需要上锁,如果cas尝试设置失败,则会跳到循环入口
                                                      //这个if中的语句是加锁成功的逻辑
                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) {//再次检查数组不为null,长度>0,检查数组中空的槽位是否为null,为nul说明这个累加单元没有别人创建,
                                                                                                                                               //因此可以将自己创建的累加单元存入累加数组中的某个槽位,如果槽位不为null,即其他线程将累加单元创建并存入,当前线程会再次进入循环入口
                        rs[j] = r;//槽位为null,填入当前线程创建的累加单元
                        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 :    //情况三:累加数组已经存在,且累加单元也已经创建好了:调用累加单元a的cas方法,在其原来的值上进行累加,如果操作成功,跳出循环
                                 fn.applyAsLong(v, x))))
        break;
    else if (n >= NCPU || cells != as) //累加失败 :检查线程数是否>cpu个数,如果大于将  collide = false;  下个  else if (!collide)中,对其取反,则不会进入     else if (cellsBusy == 0 && casCellsBusy()) {该语句块
        collide = false;            // At max size or stale
    else if (!collide)
        collide = true;
    else if (cellsBusy == 0 && casCellsBusy()) {//没有超过cpu上限:判断cellsBusy == 0 表示其他线程未对其加锁,然后用cas尝试给标记位加锁,加锁失败,不会进入该if语句块,还是会去尝试改变这个线程累加单元,然后再次进入循环入口
        try {
            if (cells == as) {      // Expand table unless stale   cas加锁成功——进入扩容逻辑
                Cell[] rs = new Cell[n << 1];  //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——扩容成功,相当于给当前线程的累加单元找一个新的槽位,continue意味着再次进入循环入口,进行下一个循环,这轮循环可能就会创建一个新的累加数组,如果该数组有空的槽位,则会累加成功
    }
    h = advanceProbe(h); //超过cpu核心数,就去尝试改变这个线程累加单元(也就是这个累加单元总是失败,那就换一个累加单元好了),然后再次进入循环入口
}




else if (cellsBusy == 0 && cells == as && casCellsBusy()) {//情况一:Cell数组还没有创建,为null: 有三个条件  :cellsBusy==0:表示未加锁
                                                                                                                                                                             cells==as: 如果其他线程创建了cells,就会将其赋值给as,因此,如果cells==as就表示还没有其他线程创建数组
                                                                                                                                                                              casCellsBusy():该函数的作用就是尝试使用cas方法将cellsBusy由0改为1,说明加锁成功了,如果加锁失败,则如法进入这个if,会进入下一个与其平级的else if
 boolean init = false;
    try {                           // Initialize table
        if (cells == as) {     //加锁成功后再次判断是否有其他线程将数组创建好了,如果相等,表示没有其他线程将cell创建好,因此自己创建
            Cell[] rs = new Cell[2];   //创建一个初始大小为2的Cell累加单元数组
            rs[h & 1] = new Cell(x);  //再创建累加单元,并将初始的x作为初始值给累加单元:表明累加成功
            cells = rs; //把刚刚创建的累加数组赋值给cells成员变量
            init = true;
        }
    } finally {
        cellsBusy = 0;//将标记位设为0,表示解锁
    }
    if (init)
        break;  //创建数组成功,累加成功,退出循环
}


else if (casBase(v = base, ((fn == null) ? v + x :  fn.applyAsLong(v, x))))    //还没有创建cell,去加锁尝试创建时失败进入这个else if,它将在base基础累加单元上累加期望值,累加成功后跳出循环,累加失败会再次进入循环入口                  
    break;                          // Fall back on using base   //累加成功则跳出循环

}

情况一:Cell为null,数组还没有创建后——>面两个else if对应的逻辑图:
在这里插入图片描述

情况三:累加数组已经存在,且累加单元也已经创建好了——>逻辑图
在这里插入图片描述

最终累加完成,需要将这些累加单元的和综合起来:通过sum()完成:
在这里插入图片描述
public long sum() {
Cell[] as = cells; Cell a; //将累加单元数组赋值给临时变量as
long sum = base; //初始值来自基础累加单元
if (as != null) { //当累加数组不为null
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null) //将每个数组元素取出,不为null则去累加
sum += a.value; //将累加单元的值累加到基础累加单元值上
}
}
return sum; //返回累加完成的和
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值