ConcurrentHashmap中的size()方法簡單解釋

本文所有的源碼都是基於JDK1.8

ConcurrentHashmap中的size()方法源碼:

public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }

final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

根據JDK1.8的註解,當你想大致瞭解ConcurrentHashmap的容器大小時,建議使用mappingCount()方法。源碼如下:

/**
     * Returns the number of mappings. This method should be used
     * instead of {@link #size} because a ConcurrentHashMap may
     * contain more mappings than can be represented as an int. The
     * value returned is an estimate; the actual count may differ if
     * there are concurrent insertions or removals.
     *(大致的意思是:返回容器的大小。這個方法應該被用來代替size()方法,因爲
     * ConcurrentHashMap的容量大小可能會大於int的最大值。
     * 返回的值是一個估計值;如果有併發插入或者刪除操作,則實際的數量可能有所不同。)
     * @return the number of mappings
     * @since 1.8
     */
    public long mappingCount() {
        long n = sumCount();
        return (n < 0L) ? 0L : n; // ignore transient negative values
    }

其實baseCount就是記錄容器數量的,直接放回baseCount不就可以了嗎?爲什麼sumCount()方法中還要遍歷counterCells數組,累加對象的值呢?

其中:counterCells是個全局的變量,表示的是CounterCell類數組。CounterCell是ConcurrentHashmap的內部類,它就是存儲一個值。

/**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;
/**
     * A padded cell for distributing counts.  Adapted from LongAdder
     * and Striped64.  See their internal docs for explanation.
     */
    @sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

JDK1.8中使用一個volatile類型的變量baseCount記錄元素的個數,當插入新數據put()或則刪除數據remove()時,會通過addCount()方法更新baseCount:

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;

    //U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 每次竟來都baseCount都加1因爲x=1
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            //多線程CAS發生失敗的時候執行
            fullAddCount(x, uncontended);//2
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        //當條件滿足開始擴容
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {//如果小於0說明已經有線程在進行擴容操作了
                //一下的情況說明已經有在擴容或者多線程進行了擴容,其他線程直接break不要進入擴容操作
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))//如果相等說明擴容已經完成,可以繼續擴容
                    transfer(tab, nt);
            }
            //這個時候sizeCtl已經等於(rs << RESIZE_STAMP_SHIFT) + 2等於一個大的負數,這邊加上2很巧妙,因爲transfer後面對sizeCtl--操作的時候,最多隻能減兩次就結束
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

1、初始化時counterCells爲空,在併發量很高時,如果存在兩個線程同時執行CAS修改baseCount值,則失敗的線程會繼續執行方法體中的邏輯,執行fullAddCount(x, uncontended)方法,這個方法其實就是初始化counterCells,並將x的值插入到counterCell類中,而x值一般也就是1,這單可以從put()方法中得知。

final V putVal(K key, V value, boolean onlyIfAbsent) {
...
//1就是要CAS 更新baseCount的值,binCount代表此鏈表或樹的值,一般都大於0.
   addCount(1L, binCount);
    return null;
}

所以counterCells存儲的都是value爲1的CounterCell對象,而這些對象是因爲在CAS更新baseCounter值時,由於高併發而導致失敗,最終將值保存到CounterCell中,放到counterCells裏。這也就是爲什麼sumCount()中需要遍歷counterCells數組,sum累加CounterCell.value值了。

參考博客:
談談 ConcurrentHashMap1.7 和 1.8 的不同實現

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