Simple analysis of LongAdder

The above introduces the atomic operation class AtomicXXX atomic operation class of java multi-threaded atomic class, ABA problem and atomic update field class detailed explanation , next we look at the LongAdder newly added in JDK8

LongAdder

1. Thoughts

  1. Update the value of base without competition

  2. When there is competition, the idea of ​​segmentation is adopted, and each thread is mapped to a different Cell to update

    The final result is the value of base + each sub-cell

Therefore, when the competition is fierce, modify a value, the probability of collision is very high, and continuous CAS is required, resulting in a lot of time spinning. However, if a value is divided into multiple values ​​by using the segmentation idea, the pressure is dispersed, and the performance Will become higher.

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

2. Source code analysis

public class LongAdder extends Striped64 implements Serializable {
    public LongAdder() {
    }
}

You can see the LongAdderinheritanceStriped64

The LongAdder base, Cellclasses, longAccumulatemethods are in the Striped64definition.

2.1 Striped64

Properties and inner classes
//使用Contended注解修饰,解决了value的伪共享问题  
@sun.misc.Contended static final class Cell {
      	//volatile修饰的值,就是要存储的long类型的值
        volatile long value;
        Cell(long x) { value = x; }
      //CAS更新value
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
      	//value的内存偏移地址
        private static final long valueOffset;
        static {
            try {
                //获取unsafe实例
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                //将value的内存偏移地址保存到valueOffset
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

    /**CPUS的数量,要限制表大小 */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * 当存在过竞争后,每个Cell数组每个元素存储各个段位的值
     * 如果为非空,说明存在过竞争,且大小为2的幂,
     */
    transient volatile Cell[] cells;

    /**
     * 基本值,主要在没有争用时使用,也用作表初始化过程中的后备。通过CAS更新。
     */
    transient volatile long base;

    /**
     * 用于初始化和调整表的大小或创建单元时使用的自旋锁(通过CAS锁定)。
    无需阻塞锁;当锁不可用时,线程会尝试其他cell或base。
     */
    transient volatile int cellsBusy;

Memory offset addresses for several attributes:

// Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long BASE;
    private static final long CELLSBUSY;
    private static final long PROBE;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> sk = Striped64.class;
            BASE = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("base"));
            CELLSBUSY = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("cellsBusy"));
            Class<?> tk = Thread.class;
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

PROBEThe "Thread" probe field maintained by ThreadLocalRandom is used as a hash code for each thread .

LongAdder method
add
public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null //1.cells数组非空,说明存在多线程竞争
            || !casBase(b = base, b + x)) {//2.如果cells为空,则就cas更新base值,如果失败,说明base已经被另外的线程动过
            //3.字面义,为true说明没有竞争,为false就是多个线程在竞争操作
            boolean uncontended = true;
            //满足if的条件,都要调用longAccumulate处理
            if (as == null //4. cells为空,即上面cas更新base失败
                || (m = as.length - 1) < 0 //5.celss长度小于1,emm跟4差不多意思
                ||(a = as[getProbe() & m]) == null ||//6.当前线程维护的cell为空
                !(uncontended = a.cas(v = a.value, v + x)))//7.cas更新当前线程维护的cell的值,如果更新失败,即uncontented为false,说明有多个线程在竞争
                longAccumulate(x, null, uncontended);
        }
    }

getProbe()

Get the hash code of the current thread, which element of the Cell array the thread wants to maintain.

static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
}

longAccumulate()

The method is Striped64in

 final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
     	/*
     		类注释中有说明,若当前cell的probe没有初始化时,为0值,就要将其初始化为尽量不与其他对象冲突的值
     	*/
        if ((h = getProbe()) == 0) {
            //调用ThreadLocalRandom初始化
            ThreadLocalRandom.current(); // force initialization
            //初始化后重新获取probe
            h = getProbe();
            //probe是0,那就说明是没有竞争,置为true
            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) {//cells已经初始化
                if ((a = as[(n - 1) & h]) == null) {//当前线程维护的cell段未初始化
                    if (cellsBusy == 0) {       // 为0时,说明没有线程获取该锁去创建cell或者在扩容cells,则以要增加的值x作为当前cell维护的值,尝试创建新的cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 &&
                            casCellsBusy()) {//cellBusy为0时,尝试CAS更新其为1,即尝试获取该自选锁
                            //是否成功创建cell
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&//cells非空
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {//且当前cell为空
                                    rs[j] = r;//将新的cell让在该位置
                                    created = true;//创建成功
                                }
                            } finally {
                                //重置为0,释放该自选所
                                cellsBusy = 0;
                            }
                            //如果创建成功,就break退出循环
                            if (created)
                                break;
                            //如果创建失败,即当前cell非空则继续
                            continue;           
                        }
                    }
                    //没有出现竞争,不需要扩容
                    collide = false;
                }
                else if (!wasUncontended)       // 前面add方法cas更新value失败
                    wasUncontended = true;      // 重置为true,后面会重hash,做简单自旋
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))//再次对当前线程维护的cell更新值,作累加,CAS成功就退出
                    break;
                else if (n >= NCPU //cells数组长度超过了CPU核数 
                         || cells != as)//或cells数组已经扩容
                    collide = false;       //置为false,表明不要扩容 ,就不会走到下面那个else if
                else if (!collide)//cas失败且没有满足上面的条件,说明有竞争
                    collide = true;//置为true,这样下次就可以直接进入下一个else if进行扩容
                else if (cellsBusy == 0 && casCellsBusy()) {//尝试获取锁
                    try {
                        if (cells == as) {      // 如果还没有其他线程扩容,则进行扩容2倍
                            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
                }
                //更新probe值
                h = advanceProbe(h);
            }
            //cells为空,因此要初始化数组,cas获取cellsBusy锁
            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;
            }
            //CAS获取cellsBusy锁失败,说明其他线程在更新当前线程维护的cell或扩容cells,则cas更新base值,更新成功则退出
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

Process:

  1. If cas fails to update the base in add, it indicates that there is competition. In this method, first probe is used to determine whether the hash of the current thread is 0. If it is 0, the probe is forced to be initialized; at this time, the cells are empty, try to obtain the lock to initialize the cells , And put the new value of x in the cell, exit
  2. If the initialization for cas cellsBusyfails to acquire the lock, it means that a thread is creating a cell or initializing it, then try to update base, and if the update is successful, exit, otherwise continue the loop.
  3. If the cells array has been initialized, if the cell maintained by the current thread is not initialized, try to obtain the lock to create the cell object, the value is x, and exit if the creation is successful; otherwise, other threads have been created and continue to loop
  4. Otherwise, if the cell maintained by the current thread has been created, the CAS is updated, and if successful, it exits; otherwise, it indicates that there are multiple threads competing, and the hash of the current thread is continued.
  5. 4 CAS will be updated again after failure, if it still fails, it will try to acquire lock expansion (the expansion ID has been set to true last time), the expansion size is 2 times (n << 1), successful expansion will be recalculated Thread hash, CAS update again
Published 75 original articles · won praise 13 · views 8369

Guess you like

Origin blog.csdn.net/weixin_43696529/article/details/105108346