原子操作類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的問題。

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