LongAdder源码分析

LongAdder的由来

LongAdder是jdk1.8新增的一个原子性的操作,AtomicLong也是一个原子性的操作,AtomicLong使用CAS算法,尝试修改值,但是在修改失败后一直处于自旋修改,非常消耗CPU资源,正是这个原因,在高并发多个线程同时操作同一个资源会造成大量的线程修改值失败,大量线程处于自旋修改值状态,严重浪费CPU资源,直接降低并发性,AtomicLong在高并发多线程修改同一个资源变量,导致性能下降,那么能不能把这个同一个资源变量分解成多个资源变量,让多线程修改多个变量,这样性能不就可以提高了吗?LongAdder正是解决这个问题诞生。

LongAdder原理图与AtomicLong原理图对比
图片.png

AtomicLong在高并发多线程情况下操作的同一个变量,LongAdder在高并发多线程情况下将变量分解成多个Cell变量进行操作,最终统计各个Cell的值与Base值相加得到最终的变量值,这种不能保证实时准确性,但最终一致性是可以保证的。

分析LongAdder类关系
public LongAdder() {
    }
public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;
    //加操作
    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);
        }
    }
    //计算sum值,将base与cells数组的各个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;
    }
    //清除cells数组的各个cell的值
    public void reset() {
        Cell[] as = cells; Cell a;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    a.value = 0L;
            }
        }
    }
    //计算sum值并清除cells数组各个cell的值
    public long sumThenReset() {
        Cell[] as = cells; Cell a;
        long sum = base;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null) {
                    sum += a.value;
                    a.value = 0L;
                }
            }
        }
        return sum;
    }
}
abstract class Striped64 extends Number {
    //静态内部类Cell,属性有Long 类型的value,使用 @sun.misc.Contended防止cpu缓存伪共享
    @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);
        }

        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

    static final int NCPU = Runtime.getRuntime().availableProcessors();
   //Striped64内部维护cell数组
    transient volatile Cell[] cells;
  //共享变量的基础值base
    transient volatile long base;
  //操作各个cell以及扩容cells数组需要的锁
    transient volatile int cellsBusy;

    Striped64() {
    }
}

LongAdder类继承了Striped64这个类,Striped64有三个属性cells数组,共享变量基础值base,操作cell和扩容需要用到的锁cellsBusy,一个内部类Cell,Cell类属性有个long类型的value,高并发情况下多线程操作的是cell单元格value进行加减操作。接下来正是进行LongAdder源码分析。

LongAdder的add方法
 public void add(long x) {
       /**
        *as 表示cells数组
        *b 表示共享变量基础值
        *v 表示当前线程命中的cells数组中的某个cell的属性值
        *m 表示cells数组长度
        *a 表示当前线程命中的cells数组中的某个cell
        **/
        Cell[] as; long b, v; int m; Cell a;
       /**
        *条件1:cells数组不为空
        *            如果为空说明可能不存在共享变量竞争(需要进行条件2进行判断)
        *            如果为不为空说明存在共享变量竞争
        *条件2:cas修改base值是否成功(base与x相加)
        *            如果修改成功直接返回即可
        *            如果修改失败存在竞争
        **/
        if ((as = cells) != null || !casBase(b = base, b + x)) {
           //uncontended 默认设置true表示未竞争共享变量
            boolean uncontended = true;
           /*
           *条件1: as是否空或者as数组长度是否为0
          *            true cells数组未初始化
          *            false已初始化
           *条件2: 当前线程hash值与上as数组长度最终命中的cell对象是否为空
          *            true 命中的cell未创建需要创建
          *            false 命中的cell已创建
           *条件3: 将当前线程命中的cell进行cas修改是否成功赋值给uncontended 
            *            true 修改cell成功直接返回即可
            *            false 修改之后存在竞争,可能需要重新选址或自旋赋值
           */
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))

               /*
               *着重分析下判断为true情况
               * cells数组未初始化
               * 命中的cell未创建需要创建
               *修改之后存在竞争,可能需要重新选址或自旋赋值
               */
                longAccumulate(x, null, uncontended);
        }
    }
longAccumulate源码分析★★★★★★
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        //h 表示当前线程PROBE值 为初始化默认为0
        int h;
        //判断当前线程PROBE值是否为0
        if ((h = getProbe()) == 0) {
            //随机分配当前线程的PROBE值
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            //这里为什么设置不存在竞争????
            /*
            * 只有这几种情况下才会进入longAccumulate方法内 :
                1. cells数组未初始化
                2. 命中的cell未创建需要创建 
                3. 修改之后存在竞争,可能需要重新选址或自旋赋值
            * 当前线程PROBE值为0,只有两个条件才会进入,1,3两种情况,3的这种可能性大很多,
            *在高并发情况下,多线程的PROBE值都是未初始化的,对应的值都是0,
            *都会对cells数组0下标cell单元进行赋值,cas必定失败较多,
            *这里就重新将PROBE赋值重新选址判断是否有竞争,提高性能减少自旋次数。
            */
            wasUncontended = true;
        }
        //collide 表示是否有竞争
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
          /**
            *as 表示cells数组
            *a 表示当前线程命中的cells数组中的某个cell
            *n表示共享变量基础值
            *v 表示当前线程命中的cells数组中的某个cell的属性值
            **/
            Cell[] as; Cell a; int n; long v;
            /**
              * 1. cells数组不为空 并且 长度>0
              */
            if ((as = cells) != null && (n = as.length) > 0) {
                /*判断当前线程PROBE与上cells数组长度-1 命中Cell是否为空
                 *   为空赋值
                 *   不为空存在竞争
                 */
                if ((a = as[(n - 1) & h]) == null) {
                    // 判断是否已上锁
                    if (cellsBusy == 0) {       // Try to attach new Cell
                         //创建新的Cell对象
                        Cell r = new Cell(x);   // Optimistically create
                       //再次判断是否无锁状态并且进行上锁
                       //     true 上锁成功
                       //     false 存在竞争 修改collide = false重新计算h值
                        if (cellsBusy == 0 && casCellsBusy()) {
                            //设置创建为false
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                /*
                                * 判断cells是否为空
                                * 当前线程命中的Cell是否为空
                                *    true 将创建的Cell赋值给命中的下标数组中
                                *    false 存在竞争创建失败 中止本次循环
                                */
                                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
                        }
                    }
                     //设置存在竞争为false重新计算h值进入下次循环
                    collide = false;
                }
                 /*当前线程命中的Cell不为空存在竞争
                 * 判断wasUncontended是否存在竞争
                 *      wasUncontended=true 不存在竞争
                 *      wasUncontended=false 存在竞争设置wasUncontended=true(调用advanceProbe(h)重新选址for循环在执行一次)
                 */         
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                /*当前线程命中的Cell不为空存在竞争
                 * 判断wasUncontended=true
                 * 进行自旋将x赋值给当前线程命中的Cell的value
                 *       true 赋值成功跳出循环
                 *       false 失败是否需要扩容
                 */      
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                /*当前线程命中的Cell不为空存在竞争
                 * 判断wasUncontended=true
                 * 进行自旋将x赋值给当前线程命中的Cell的value失败
                 * 判断 数组长度是否>=cpu核数 或者 cells数组与as是否相等 
                 *      true (数组长度>=cpu核数 或者 cells和as不相等已扩容)
                 *      true (数组长度<cpu核数 或者 cells和as相等 可能需要扩容)
                 */      
                else if (n >= NCPU || cells != as)
                    //设置竞争为false 
                    collide = false;            // At max size or stale
                else if (!collide)
                      //设置竞争为ture 重新计算h值 下次进入此步骤直接跳过,判断是否需要扩容
                    collide = true;
                 /*
                 * cellsBusy == 0  当前是否处于无锁状态
                 *casCellsBusy() 加锁是否成功
                 *       true 无锁状态&上锁成功  进行扩容 
                 *       false 已加锁 或者 上锁失败 重新计算h值
                 */
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                         // DK(Double Check 判断cells与as是否相等) 
                        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
                            cells = rs;
                        }
                    } finally {
                        // 释放锁
                        cellsBusy = 0;
                    }
                    //中止本次循环不计算h值
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
             /**
               *  2. 处于无锁状态 &  cells==as ==null(无线程初始化cells)&& 上锁成功(初始化cells数组)
               */
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    //DK(double check cells与as是否相等 在上锁过程中可能其他线程已经初始化cells数组了)
                    if (cells == as) {
                        //初始化cells数组长度为2
                        Cell[] rs = new Cell[2];
                        //创建Cell将x赋值给value并将当前线程PROBE与上1赋值给cells数组
                        rs[h & 1] = new Cell(x);
                        //将rs引用指向cells
                        cells = rs;
                        //初始化成功
                        init = true;
                    }
                } finally {
                    // 释放锁
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
             /**
               *  3.  cells为空,处于加锁状态 或者 cells != as或者在加锁过程中失败了
               *  将x值加入到base中
               */
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

LongAdder初始化cells数组条件
1.cells为空,获取锁成功
LongAdder发生扩容条件
1.cells数组不为空,长度>0
2.存在Cell竞争
3.更新当前线程命中的Cell单元值失败
4.cells数组长度<cpu核数
5.持有锁成功

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270