ConcurrentHashMapのaddCountメソッドの動作解析

ConcurrentHashMapの内部処理の中で、要素数を管理するaddCountメソッドは非常に重要な役割を果たします。本稿では、このメソッドがどのように動作するのか、コードを交えて詳しく解説します。このメソッドは主にputValメソッドから呼び出されます。

ソースコードの動作分析

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

    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        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))) {
            fullAddCount(x, uncontended);
            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) {
                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);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

CounterCell配列の役割

ConcurrentHashMapでは、要素数のカウントにCounterCell配列を採用しています。通常のコレクションであれば、単一のsize変数で管理するのが一般的ですが、マルチスレッド環境ではその変数へのアクセス競合が激しくなり、パフォーマンスが低下する可能性があります。そこでConcurrentHashMapは、カウントを複数のセルに分散させることで、競合を緩和しています。

public class ConcurrentHashMap<K,V> extends Abstract{
    // セル配列の初期化・拡張中を示すCASフラグ
    private transient volatile int cellsBusy;
    // 要素数を分散して保持するセル配列
    private transient volatile CounterCell[] counterCells;

    @sun.misc.Contended 
    static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

    // 全セルの値を合計して要素数を取得
    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;
    }
}

上記の通り、CounterCell配列の各要素が個別にカウントを保持し、size()メソッドではそれらを合算することで全体の要素数を算出します。

addCountメソッドの処理概要

addCountメソッドは主に以下の2つの処理を行います。

  • テーブルの要素数を1増加させる:baseCountのCAS更新を試み、失敗した場合はCounterCellを使用して分散更新を行います。
  • リサイズの必要性を確認し、必要な場合は自らリサイズを開始するか、進行中のリサイズに参加する。

注目すべき点は、リサイズ開始時にsizeCtlの下位16ビットが2増加する点です。これは、リサイズが完了した際にsizeCtlがrs + 1になることを意味し、sc == rs + 1はリサイズ終了の合図となります。また、リサイズに参加できるスレッド数の上限が65535であるのも、下位16ビットの表現範囲に起因します。

タグ: ConcurrentHashMap addCount CounterCell cas リサイズ

5月24日 08:16 投稿