LongAdder的原理

本文详细解析了LongAdder的内部工作原理,包括其如何分散热点、使用Striped64基类的结构设计以及避免并发竞争的策略。重点介绍了casCellsBusy方法的作用和空间换时间思想的应用。
摘要由CSDN通过智能技术生成

目录

LongAdder的原理

1、LongAdder实例的内部结构

2、基类Striped64内部三个重要的成员

3、LongAdder类的longValue()方法

4、LongAdder类的add()方法

5、LongAdder类中的longAccumulate()方法

6、LongAdder类的casCellsBusy()方法

LongAdder的原理

AtomicLong 使用内部变量value保存着实际的long值,所有的操作都是针对该value变量进行的。也就是说,在高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。重试线程越多,就意味着CAS的失败概率更高,从而进入恶性CAS空自旋状态。

LongAdder 的基本思路是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽(元素)中,各个线程只对自己槽中的那个值进行CAS操作。这样热点就被分散了,冲突的概率就小很多。

使用LongAdder,即使线程数再多也不必担心,各个线程会分配到多个元素上去更新,增加元素个数,就可以降低value的“热度”,AtomicLong中的恶性CAS空自旋就解决了。

如果要获得完整的LongAdder存储的值,只要将各个槽中的变量值累加,返回最终累加之后的值即可。

LongAdder的实现思路与ConcurrentHashMap中分段锁的基本原理非常相似,本质上都是不同的线程在不同的单元上进行操作,这样减少了线程竞争,提高了并发效率。LongAdder的设计体现了空间换时间的思想,不过在实际高并发场景下,数组元素所消耗的空间可以忽略不计。

LongAdder的设计体现了空间换时间的思想,不过在实际高并发场景下,数组元素所消耗的空间可以忽略不计。

1、LongAdder实例的内部结构

LongAdder的内部成员包含一个base值和一个cells数组。在最初无竞争时,只操作base的值;当线程执行CAS失败后,才初始化cells数组,并为线程分配所对应的元素。

LongAdder中没有类似于AtomicLong中的getAndIncrement()或者incrementAndGet()这样的原子操作,所以只能通过increment()方法和longValue()方法的组合来实现更新和获取的操作。

2、基类Striped64内部三个重要的成员

     /**
     * 成员一:存放Cell的哈希表,大小为2的幂
     */
     transient volatile Cell[] cells;
     /**
     * 成员二:基础值
     * 1. 在没有竞争时会更新这个值
     * 2. 在cells初始化时,cells不可用,也会尝试通过CAS操作值累加到base
     */
     transient volatile long base;
     
     /**
     * 自旋锁,通过CAS操作加锁,
     * 为0表示cells数组没有处于创建、扩容阶段
     * 为1表示正在创建或者扩展cells数组,不能进行新Cell元素的设置操作
     */
     transient volatile int cellsBusy;

Striped64内部包含一个base和一个Cell[]类型的cells数组,cells数组又叫哈希表。在没有竞争的情况下,要累加的数通过CAS累加到base上;如果有竞争的话,会将要累加的数累加到cells数组中的某个Cell元素里面。所以Striped64的整体值value为base+∑[0~n]cells。

Striped64的设计核心思路是通过内部的分散计算来避免竞争,以空间换时间。LongAdder的base类似于AtomicInteger里面的value,在没有竞争的情况下,cells数组为null,这时只使用base进行累加;而一旦发生竞争,cells数组就上场了。

cells数组第一次初始化长度为2,以后每次扩容都变为原来的两倍,一直到cells数组的长度大于等于当前服务器CPU的核数。为什么呢?同一时刻能持有CPU时间片去并发操作同一个内存地址的最大线程数最多也就是CPU的核数。

在存在线程争用的时候,每个线程被映射到cells[threadLocalRandomProbe &cells.length]位置的Cell元素,该线程对value所做的累加操作就执行在对应的Cell元素的值上,最终相当于将线程绑定到cells中的某个Cell对象上。

3、LongAdder类的longValue()方法

         public long longValue() {
             // longValue()方法调用了sum(),累加所有Cell的值
             return sum();
         }

         /**
          * 将多个cells数组中的值加起来的和就类似于AtomicLong中的value
          */
         public long sum() {
             Cell[] as = cells;
             Cell a;
             long sum = base;
             if (as != null) {
                 // 累加所有cell的值
                 for (int i = 0; i < as.length; ++i) {
                     if ((a = as[i]) != null)
                         sum += a.value;
                 }
             }
             return sum;
         }

4、LongAdder类的add()方法

         /**
          * 自增1
          */
         public void increment() {
             add(1L);
         }
     
         /**
          * 自减1
          */
         public void decrement() {
             add(-1L);
         }
     
       public void add(long x) {
             Cell[] as; long b, v; int m; Cell a;
             if (
                 // CASE 1: cells数组不为null,说明存在争用;在不存在争用的时候,cells数组一定为null,一旦对base的cas操作失败,才会初始化cells数组。
                 (as = cells) != null ||    
                 // CASE 2: 如果cells数组为null,表示之前不存在争用,并且此次casBase执行成功,表示基于base成员累加成功,add方法直接返回;
                 // 如果casBase方法执行失败,说明产生了第一次争用冲突,需要对cells数组初始化,此时即将进入内层if块。                            
                 !casBase(b = base, b + x)) {
                      
                      if (
                          // CASE 3: cells没有初始化
                          as == null || (m = as.length - 1) < 0 || 
                          // CASE 4: 指当前线程的hash值在cells数组映射位置的Cell对象为空,意思是还没有其他线程在同一个位置做过累加操作。
                          (a = as[getProbe() & m]) == null ||     
                          // CASE 5:指当前线程的哈希值在cells数组映射位置的Cell对象不为空,然后在该Cell对象上进行CAS操作,
                          // 设置其值为v+x(x为该Cell需要累加的值),但是CAS操作失败,表示存在争用。        
                          !(uncontended = a.cas(v = a.value, v + x)))
                     // 如果CASE 3、CASE 4、CASE 5有一个为真,就进入longAccumulate()方法
                     longAccumulate(x, null, uncontended);
             }
         }

casBase方法很简单,就是通过UNSAFE类的CAS设置成员变量base的值为base+x(要累加的值)。

         /**
          * 使用CAS来更新base值
          */
         final boolean casBase(long cmp, long val) {
             return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
         }

5、LongAdder类中的longAccumulate()方法

longAccumulate()是Striped64中重要的方法,实现不同的线程更新各自Cell中的值,其实现逻辑类似于分段锁,具体的代码如下:、

    final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }

        // 扩容意向,collide=true可以扩容,collide=false不可扩容
        boolean collide = false;
        // 自旋,一直到操作成功
        for (; ; ) {
            // as 表示cells引用
            // a 表示当前线程命中的Cell
            // n 表示cells数组长度
            // v 表示期望值
            Cell[] as;
            Cell a;
            int n;
            long v;

            // CASE1: 表示cells已经初始化了,当前线程应该将数据写入对应的Cell中
            // 这个大的if分支有 3 个小分支
            if ((as = cells) != null && (n = as.length) > 0) {
                
                // CASE1.1: true表示下标位置的Cell为null,需要创建new Cell
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // cells数组没有处于创建、扩容阶段
                        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;
                }

                // CASE1.2:当前线程竞争修改失败,wasUncontended为false
                // wasUncontended是add(…)方法传递进来的参数如果为false,
                // 就表示cells已经被初始化,并且线程对应位置的Cell元素也已经被初始化,但是当前线程对Cell元素的竞争修改失败。
                // 如果add方法中的条件语句CASE 5通过CAS尝试把cells[m%cells.length]位置的Cell对象的value值设置为v+x而失败了,
                // 就说明已经发生竞争,就将wasUncontended设置为false。
                // 如果wasUncontended为false,就需要重新计算prob的值,那么自旋操作进入下一轮循环。
                else if (!wasUncontended)              // CAS already known to fail
                    wasUncontended = true;             // Continue after rehash

                // CASE 1.3:当前线程rehash过哈希值,CAS更新Cell
                // 无论执行CASE1分支的哪个子条件,都会在末尾执行h=advanceProb()语句
                // rehash出一个新哈希值,然后命中新的Cell,
                // 如果新命中的Cell不为空,在此分支进行CAS更新,将Cell的值更新为a.value+x,
                // 如果更新成功,就跳出自旋操作;否则还得继续自旋。
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                        fn.applyAsLong(v, x))))
                    break;

                // CASE 1.4:调整扩容意向,然后进入下一轮循环
                // 条件1:如果n≥NCPU条件成立,就表示cells数组大小已经大于等于CPU核数,扩容意向改为false,表示不扩容了;
                // 如果该条件不成立,就说明cells数组还可以扩容,
                // 条件2:如果cells != as为true,就表示其他线程已经扩容过了,也会将扩容意向改为false,表示当前循环不扩容了。
                // 当前线程调到CASE1分支的末尾执行rehash操作重新计算prob的值,然后进入下一轮循环。
                else if (n >= NCPU || cells != as)
                    collide = false;   // 达到最大值,或者as值过期

                // CASE 1.5: 如果!collide=true满足,就表示扩容意向不满足,
                // 设置扩容意向为true,但是不一定真的发生扩容
                // 然后进入CASE1分支末尾重新计算prob的值,接着进入下一轮循环。
                if (!collide)
                    collide = true;

                // CASE 1.6:真正扩容的逻辑
                // 条件1:cellsBusy==0为true表示当前cellsBusy的值为0(无锁状态),当前线程可以去竞争这把锁
                // 条件2:casCellsBusy()表示当前线程获取锁成功,CAS操作cellsBusy改为0成功,可以执行扩容逻辑。
                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);           //重置(rehash)当前线程Hash值
            }

            // CASE 2:cells还未初始化(as 为null),并且cellsBusy加锁成功,初始化cells
            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;
            }

            // CASE 3:当前线程cellsBusy加锁失败,表示其他线程正在初始化cells
            // 所以当前线程将值累加到base,注意add(…)方法调用此方法时fn为null
            else if (casBase(v = base, ((fn == null) ? v + x :
                    fn.applyAsLong(v, x))))
                break;   // 在 base操作成功时跳出自旋
        }
    }

6、LongAdder类的casCellsBusy()方法

casCellsBusy()方法的代码,就是将cellsBusy成员的值改为1,表示目前的cells数组在初始化或扩容中

         final boolean casCellsBusy() {
             return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
         }

casCellsBusy()相当于锁的功能: 当线程需要cells数组初始化或扩容时,需要调用casCellsBusy(),通过CAS方式将cellsBusy成员的值改为1,如果修改失败,就表示其他的线程正在进行数组初始化或扩容的操作。只有CAS操作成功,cellsBusy成员的值被改为1,当前线程才能执行cells数组初始化或扩容的操作。在cells数组初始化或扩容的操作执行完成之后,cellsBusy成员的值被改为0,这时不需要进行CAS修改,直接修改即可,因为不存在争用。

当cellsBusy成员值为1时,表示cells数组正在被某个线程执行初始化或扩容操作,其他线程不能进行以下操作:

(1)对cells数组执行初始化。

(2)对cells数组执行扩容。

(3)如果cells数组中某个元素为null,就为该元素创建新的Cell对象。因为数组的结构正在修改,所以其他线程不能创建新的Cell对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值