原子操作类LongAdder

LongAdder是一个原子性递增或者递减类,与AtomicLong的不同之处在于,LongAdder能够解决高并发情况下多线程同时竞争同意资源,而带来性能降低的问题。主要是通过将一个变量拆分为多个Cell,从而将单个变量的并发数分散到多个Cell上。关于LongAdder实现的几个分体如下:
(1)LongAdder 的结构是怎样的?
(2)当前线程应该出问Cell 数组里面的哪一个Cell元素?
(3)如何初始化Cell数组?
(4)Cell 数组如何扩容?
(5)线程访问分配的Cell元素有冲突后如何处理?
(6)如何保证线程操作被分配的Cell元素的原子性?

LongAdder的内部结构是LongAdder 继承Striped64实现Serializable,保证线程操作被分配的Cell元素的原子性可以看Cell的源码进行分析。

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

        // Unsafe mechanics
        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);
            }
        }
    }

从源码中可以看到Cell类上的注解@sun.misc.Contended,用于防止伪共享的; volatile修饰的变量value,保证在多线程情况下value的内存可见性;cas方法保证当前线程更新是分配Cell元素中value的原子性。到这里问题1和6都已解决,下面就看看LongAdder类的递增或者递减方法的源码。

public void increment() {
        this.add(1L);
    }

    public void decrement() {
        this.add(-1L);
    }
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //(0)cells如果为null,则在运行casBase方法,在base上进行累加
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 || //(1)判断cells数组是非未空或者元素数量为0
                (a = as[getProbe() & m]) == null || // (2)通过as[getProbe() & m]确定当前线程使用哪个cell
                !(uncontended = a.cas(v = a.value, v + x))) //(3)执行Cell类中的cas方法,更新value的值
                longAccumulte(x, null, uncontended);//(4)
        }
    }
    
final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

从源码可以看到递增和递减方法调用的都是同一个方法add,从(2)处可以回答第二个问题,当前线程如和获取cell的。代码中还能发现LongAdder中的add方法中 Cell[] as元素是通过as = cells初始化得到的,而这个cells是在Striped64类中的属性;如果if条件都不满足的请况下如何初始化cells的呢?下面从longAccumulte这个方法找寻答案。

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // 初始化,在线程选择cells中的哪个cell是会用到
            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) { //cells不为空且有元素
                if ((a = as[(n - 1) & h]) == null) { // 给当前线程分配cell失败
                    if (cellsBusy == 0) {       // cellsBusy 标识位为0说明cells数组没有被初始化或者扩容
                        Cell r = new Cell(x);   // 给当前线程创建一个新的cell
                         //再次判断cellsBusy标识位为0 且源自操作成功
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               
                          // 再次确认当前线程所在的cells数组中的位置没有cell,再将新建的cell放进相应的位置
                                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
               //调用cell的cas方法,更新value值,fn为指定的计算逻辑,如果为空则使用默认的计算逻辑//v+x,如果不为空则使用指定的fn函数计算,从LongAdder的add方法上可以看到fn是null,所以在//LongAdder中使用的是默认的计算逻辑
                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) {      //cells数组扩容为原来的两倍
                            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
                }
                //为了能够找到一个空闲的Cell ,重新计算hash值, xorshift 算法生成随机数,这样就可以解决分配cell的冲突问题
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           //初始化cells
                    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
        }
    }

从上面的源码分析中可以解决问题3 4 5的问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章