ConcurrentHashMap源碼分析(04)-size()方法

前言

HashMap.size()的代碼非常簡單,直接返回成員變量size即可。可是在ConcurrentHashMap里面,是否也是這樣呢?答案是否定的,因為我們需要考慮并發的情況。若我們繼續沿用HashMap.size()的思想,并發的效率可以想象得到,將變得非常得低效。

那么ConcurrentHashMap.size()又是怎么實現的呢?我們這一小節將進行分析~

size()

這個方法內部調用sumCount()方法,但是sumCount()返回的是long類型的,而size()返回的是int類型的,需要進行一定的適配。

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

sumCount()

這個方法用于返回節點的個數,有用到兩個關鍵的成員變量

  1. baseCount -- 基本的計數器,當沒有競爭的時候,使用這個值
  2. counterCells -- 數組結構,當有競爭的時候使用它來進行計算

sumCount()實現的邏輯如下:

  1. 首先判斷counterCells是否為null,為null說明沒有競爭,直接返回baseCount
  2. 如果counterCells不為null,說明有競爭,通過遍歷counterCells數組,將所有元素的value進行求和。
/**
 * Base counter value, used mainly when there is no contention,
 * but also as a fallback during table initialization
 * races. Updated via CAS.
 * 基本的計數器,當沒有競爭的時候,使用這個值
 */
private transient volatile long baseCount;

/**
 * Table of counter cells. When non-null, size is a power of 2.
 */
private transient volatile CounterCell[] counterCells;

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

既然競爭的時候需要用到counterCells,那么它又是什么時候生成的呢?在前面的分析我們看到了counterCells的影子,那就是在addCount()方法中,那么接下來我們就再來分析一下關于counterCells的代碼。

addCount()方法再分析

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // 1. 初始的時候,counterCells一定為null,第一個條件此時將不會被滿足
    // 2. 在第一個條件不滿足的時候,需要判斷第二個條件。
    // 3. 第二個條件在存在競爭的情況下將返回false,也就是需要進入到if里面
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        // 這兒有三個或條件,只需要滿足其一,就可以進入到if
        // 1. counterCells為null
        // 2. counterCells為空數組
        // 3. counterCells指定位置元素為null
        // 4. CAS設置counterCell.value失敗的時候
        // 這幾種情況將進入到`fullAddCount()`進行自旋操作
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    // 省略掉擴容的代碼...
}

fullAddCount()方法

這個方法非常得復雜,代碼也很長,如果深入進去將陷入到細節中去。在此我們只需要知道這個方法的用途,那就是通過自旋的方式新增或更新counterCells,使用counterCells來統計節點個數將不會得到準確的結果。

CounterCell類

既然競爭的情況下是圍繞counterCells來展開的,那么我們需要看看這個成員變量所屬的類型,CounterCell

/**
 * A padded cell for distributing counts.  Adapted from LongAdder
 * and Striped64.  See their internal docs for explanation.
 * 改編自LongAdder和Striped64
 */
@sun.misc.Contended static final class CounterCell {
    volatile long value;
    CounterCell(long x) { value = x; }
}

這個類有幾個需要注意的地方

  1. value使用volatile修飾,可保證其可見性
  2. CounterCell使用@sun.misc.Contended修飾

@sun.misc.Contended這個注釋需要明確說明一下。如果類上面使用該注釋,標識需要防止偽共享

那么什么又是偽共享呢?借鑒一下網上大神的闡述。

【說明】:偽共享(false sharing)。先引用對偽共享的解釋
緩存系統中是以緩存行(cache line)為單位存儲的。緩存行是2的整數冪個連續字節,
一般為32-256個字節。最常見的緩存行大小是64個字節。當多線程修改互相獨立的變量時,
如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是偽共享。

JDK 8版本之前沒有這個注解,Doug Lea使用拼接來解決這個問題,把緩存行加滿,讓緩存之間的修改互不影響。

mappingCount()

我們額外分析一下這個方法,這是jdk8新加的一個方法,內部同樣調用了sumCount(),和size()有同樣的效果,但是它的返回值卻是long,可以避免精度的損失。同時這個方法也是被推薦用來替代size()

/**
 * 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.
 *
 * @return the number of mappings
 * @since 1.8
 */
public long mappingCount() {
    long n = sumCount();
    return (n < 0L) ? 0L : n; // ignore transient negative values
}

總結

期初,我們還以為size()會很簡單,但是一通分析下來卻發現它并不是想象得那么簡單,尤其是addCount()內部調用的fullAddCount()方法,其復雜度不下于擴容方法

為了省事,本節并沒有對這個方法進行分析,后續有時間再來分析。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。