Javaソースコード分析容器シリーズ-ConcurrentHashMap

用法上のConcurrentHashMapとHashMapのは非常に異なっていないが、ConcurrentHashMapのは、スレッドセーフでは、マルチスレッド環境で使用することができます。この記事では、ConcurrentHashMapの独自の機能のいくつかを説明しますと、HashMapの同様の部分は省略する。

JDK1.8に基づいて、

メンバ変数と定数

コードのConcurrentHashMapの高い複雑多く、メンバ変数と定数の多くを使用し、(HashMapの既存の変数または定数は繰り返しません)を理解するのに役立ちます。

定数

// 默认并发度,同时允许多少个线程访问
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 扩容时每个线程扩容时至少要迁移的桶的数量,最低不能少于 16
private static final int MIN_TRANSFER_STRIDE = 16;
// 辅助变量,没啥用
private static int RESIZE_STAMP_BITS = 16;
// 可用于扩容的最大线程数,但一般肯定到不了这个数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 会用来计算一个标志位,实际上也没什么用
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// 下面三个常量是几个特殊的哈希值
// MOVED:表示桶正在经被迁移
// TREEBIN:表示桶正在进行树化
// RESERVED:表示节点运行 computeIfAbsent 等方法
static final int MOVED     = -1;
static final int TREEBIN   = -2;
static final int RESERVED  = -3;
// 用于计算 key 的 hash 值
static final int HASH_BITS = 0x7fffffff; 
// CPU 的数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
复制代码

変数

これらの変数は基本的に揮発性のキーワードを使用している、それは、視認性のままでなければならないため、これらの同時の環境変数です。

// 桶数组,与 HashMap 基本一致,也是延迟加载,不过这里使用了 volatile 关键字
transient volatile Node<K,V>[] table;
// 桶数组,用于扩容
private transient volatile Node<K,V>[] nextTable;
// 记录所有的元素的个数,类似于 HashMap 的 size
private transient volatile long baseCount;
// 初始化和扩容的标志位
// 默认值:0
// 初始化前:初始化容量大小
// 正在初始化:-1
// 扩容前:触发扩容操作的元素个数,相当于 HashMap 的 threshold
// 正在扩容:-(1 + 参与扩容的线程数量)
private transient volatile int sizeCtl;
// 扩容的时候需要对桶内的元素进行迁移,这个变量用来记录桶的下标,表示迁移的进度,下面会详细介绍这个变量
private transient volatile int transferIndex;
// 更新 counterCells 时使用的自旋锁
private transient volatile int cellsBusy;
// 计数用,用于计算还没来的及更新到 baseCount 中的变化
private transient volatile CounterCell[] counterCells;
复制代码

実現

HashMapのと比較すると、ConcurrentHashMapのは、スレッドセーフです。これは、コンテナへの同時アクセスの異なる部分に複数のスレッドがスレッド間の競合を減らすことができます。このコンテナは、HashMapを置き換えるために設計されていますが、マルチスレッド環境のニーズを満たすためには、2つの設計目標を持っていません。

  • コンテナは、マルチスレッドの更新競技のコストが最小化される可能にしながら、(などのget()、イテレータ、など)の同時読み取りをサポートすることができます。
  • 同じまたはより良いHashMapを持つ宇宙消費

全体的にConcurrentHashMapのは、高同時実行するだけでなく、高性能をサポートできることが必要です。具体的な実現もほぼ書き換え特にJDK1.8の変化の数、基礎をなす記憶メカニズムは完全に矛盾しています。JDK 1.7およびJDK1.8基礎となるストレージの違い:

// JDK1.7
final Segment<K,V>[] segments;
transient volatile HashEntry<K,V>[] table; // 每一个分段锁都会有一个 table
复制代码
// JDK1.8
transient volatile Node<K,V>[] table;
复制代码

JDK1.8では、並行性のより細かい粒度の一部、テーブルの長さは、以前のバージョン、セグメントの同時実行の数が同時となる、となります。

操作はループから抜け出すことによって完成されるまで、そのためCASの使用のため、運転中のConcurrentHashMapのスピンがたくさんある、操作は実際に死のスピンサイクルで、それが待機します。

ほとんど差が、しかし、それ自体とXOR(排他的OR)演算に加えて、またConcurrentHashMapのHashMapのハッシュ関数HASH_BITES計算有します。

// ConcurrentHashMap.spread()
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
复制代码

HASH_BITS バイナリ表現は次のとおりです。

01111111 11111111 11111111 11111111
复制代码

CAS

JDK1.8に前、のConcurrentHashMapの主な用途は、ロック機構をセグメント化が達成され、以降のJDK 1.8で、主に使用されるCAS(sun.misc.Unsafe)+達成同期。CASは、今日のCPUがこの機能をサポートし、CASのハードウェアをサポートする必要性、高効率のために知られている、ロックフリー並行処理技術です。

ConcurrentHashMapのは、彼らのCASを達成したが、使用しませんでしたsun.misc.Unsafe(最新のJDKがjdk.internal.misc.Unsafeに置き換えられています)。

CASのConcurrentHashMapはバケットアトミックアクセスするには、次の3つの方法を使用して達成最初の要素を

// 获取桶的某个位置,任何情况下可以使用
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectAcquire(tab, ((long)i << ASHIFT) + ABASE);
}
// 插入桶的第一个键值对,可以在并发环境下,任何情况下可以使用
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v) {
    return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 把键值对插入到桶中,只在有锁的的区域使用
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectRelease(tab, ((long)i << ASHIFT) + ABASE, v);
}
复制代码

ConcurrentHashMapのが一般的ロックバレルとして使用されているバケットの最初の要素は、特別な意味を持っています

それは他の同時更新変数で必要とされているバケットの外側に、アクセスするために使用されることにCASを追加。このようなsizeCtl変数を更新するなど:

// ConcurrentHashMap.initTable()
// 将容器的状态设置为正在扩容
U.compareAndSetInt(this, SIZECTL, sc, -1)
复制代码

非常に遅く、肥大化し、これは実際には誤解であるという印象を与えます同期し、継続的な最適化により底同期、現在のパフォーマンスはかなりリエントラントロックされています。同期化され、簡単な使用のために、それがデッドロック状態を引き起こすことはありませんので、通常の状況下では他のロックと同期させることができるロックの使用を再検討していない限り、需要を満たすことができません。

使用同期のConcurrentHashMapが比較的小さい粒子サイズであれば、それは多くはない同期コードに包まれている、それはまだ、高いパフォーマンスを維持することができます。これは、HashtableのConcurrentHashMapの間の最大の違いです。ハッシュテーブルは、スレッドセーフの使用を保証するために同期されているが、同時コンテナ全体のレベルが低いことを可能にする同期メソッドレベルで使用されます。

膨張機構

膨張は、良いサイズが事前に予測することができる非常に遅い動作である、膨張の数を削減することができます。あなたが同時アクセスをConcurrentHashMapのことができますので、HashMapの膨張機構は、多少異なっているので、拡張時に書き込みスレッドが継続することはできませんが、これらのスレッドはまた、事業の拡大に参加するために、利用することができます。

コンテナの拡張は、二つの状況に分かれています。

  • **初期化:**また、負荷遅延として知られている挿入要素を、最初の時間を初期化します
  • 拡張**:**記憶素子は、臨界値は、拡張を開始達します

初期化とこれら二つのプロセスの拡大が明らかとき、次のチャートでは、プロセス全体の外観を進める方法を、一人では存在しません。

テーブルのインスタンスのサイズを決定しますが、コンストラクタは、別の地図に渡された場合、テーブルを初期化していないだけでなく、次の拡張のための臨界点を決定するために、拡大にtryPresizeを呼び出します。

あなたの第1のインサート要素は、(遅延ロード)テーブルを初期化する場合、初期化するinitTable()を呼び出します。

初めてので、(GET()操作を除く)停止操作は、膨張プロセスに参加する場合、それは膨張され、そしてかどうかを決定するために要素を挿入していないため場合。膨張が完了した後、操作(挿入または更新)は、条件がそう、スピンによって再びツリーリストに有効にする場合、ツリーは、要素内に挿入されているかどうかを確認するために必要。挿入は、拡張要素が臨界点に等しい値よりも大きい場合、膨張開始コンテナの状態を確認するために呼び出しをaddCount()が完了した後に

初期化によってinitTable()方法を完了します。

// ConcurrentHashMap.initTable()
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    // 检查当前桶是否为空,为空则开始初始化
    while ((tab = table) == null || tab.length == 0) {
        // 发现正在初始化或者在扩容,则什么也不做,进入自旋状态等待表被初始化(或扩容)完成
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // 放弃 CPU 资源
        // 线程开始扩容时会把 sizeCtl 的值置为 -1,让其他线程发现正在进行初始化
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    // 确定初始化桶的数量,如果 sizeCtl 大于 0 则使用 sizeCtl 的值,否则使用默认容量
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // 设置扩容阀值,ConcurrentHashMap 中的装载因子仅仅在构造函数中使用
                    sc = n - (n >>> 2); // 相当于 n * 0.75
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}
复制代码

拡張操作は、以下の2つの方法で開始されます。

  • addcount
  • tryPresize

addcount()メソッドは、コンテナ要素を変更するために呼び出され、キュベットの主電流状態は容量が必要に応じて、膨張となり、必要とされているか否かが判断されます。

// ConcurrentHashMap.addcount()
// 这个方法主要用来给当前容器的数量进行计数顺便检查一下是否需要扩容
private final void addCount(long x, int check) {
    CounterCell[] cs; long b, s;
    // 给容器中的元素进行增或者减
    // 如果 cs 不为 null(说明有并发情况)或者 baseCount 增减运算失败,
    if ((cs = counterCells) != null ||
        !U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell c; long v; int m;
        boolean uncontended = true;
        // 那么就会通过 cs 来进行计数,
        // 如果 cs 是空(还不是并发)或者 (cs 中随机取余一个数组位置为空 或者 cs 这个位置的变量失败)
        // 说明通过 cs 来计数也失败了,最后才会调用 fullAddCount 来进行计数
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
                U.compareAndSetLong(c, CELLVALUE, v = c.value, v + x))) {
            // 与 LongAdder 实现一致,可以理解为并发情况下的一个计数器
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        // 统计当前节点的数量
        s = sumCount();
    }
    // 在增加元素的操作中 check 都会满足这个条件
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        // 检查扩容条件:
        // 1. 是否达到阀值: s >= sizeCtl (上文已经解释了 sizeCtl,sizeCtl 大于 0 时表示下次扩容的临界点)
        // 2. 是否可以扩容: tab != null && tab 当前的长度小于 1 << 30
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                (n = tab.length) < MAXIMUM_CAPACITY) {
            // 根据当前桶的数量生成一个标志位
            int rs = resizeStamp(n);
            // 如果正在扩容
            if (sc < 0) {
                // 检查当前扩容的进展:
                // 1. 如果 sc 的低 16 位不等于标识位( sizeCtl 变化了,说明容器状态已经变化),退出
                // 2. 如果 sc == 标识位 + 1 (通过下面代码可知,刚开始扩容时, sc = rs + 2,如果 sc = rs + 1,说明已经没有线程在扩容),退出
                // 3. 如果 sc == 标识符 + 65535,参与扩容的线程已经达到最大数量,当前线程不再参与,退出
                // 4. 如果 nextTable == null 说明扩容结束(nextTable 在扩容中起中转作用,所有的元素会被限移到 nextTable 中,最后让 tab = nextTable,nextTable == null 来完成扩容),退出
                // 5. transferIndex <= 0 说明没有桶还需要迁移了(transferIndex 用于标识当前迁移到哪个桶了,小于等于 0 说明已经迁移到最后一个桶或者已经迁移完成,迁移的顺序是从最后一个桶开始),退出。
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                // 如果迁移还是进行,当前线程尝试参与扩容
                if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 如果当前不在扩容中,则发起一个新的扩容
            else if (U.compareAndSetInt(this, SIZECTL, sc,
                                            (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            // 统计当前节点的数量
            s = sumCount();
        }
    }
}
复制代码

tryPresizeはaddcount方式に比べて、比較的単純である拡張のために試してみることです。

// ConcurrentHashMap.tryPresize()
private final void tryPresize(int size) {
    // 根据 size 计算扩容的容量
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
        tableSizeFor(size + (size >>> 1) + 1);
    int sc;
    // 判断是否可以进行扩容,如果 sizeCtl <= 0,说明已经在扩容中,那么久不会再进行扩容
    while ((sc = sizeCtl) >= 0) {
        Node<K,V>[] tab = table; int n;
        // 如果当前容器还没有初始化,则进行初始化,与 initTable 相同
        if (tab == null || (n = tab.length) == 0) {
            // 当前的扩容阀值与传入的值之间选大的作为这次初始化的大小
            n = (sc > c) ? sc : c;
            // 进入初始化状态
            if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
                try {
                    if (table == tab) {
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = nt;
                        sc = n - (n >>> 2); // 相当于 n * 0.75
                    }
                } finally {
                    sizeCtl = sc;
                }
            }
        }
        // 如果还每达到扩容的阀值或者超过了最大容量,则停止扩容
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        else if (tab == table) {
            // 开始进行扩容
            int rs = resizeStamp(n);
            if (U.compareAndSetInt(this, SIZECTL, sc,
                                    (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
        }
    }
}
复制代码

膨張により特定の操作transfer()方法を完了するために。

// ConcurrentHashMap.transfer() 该方法用于将元素都迁移到 nextTable 中
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    int n = tab.length, stride;
    // 在迁移元素时,会将桶分段,stride 表示每段的长度,最小值为 16
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; 
    // 初始化 nextTable
    if (nextTab == null) {
        try {
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        // 这个变量用于记录当前迁移的进度,需要注意的是迁移元素从最后一个桶开始
        transferIndex = n;
    }
    int nextn = nextTab.length;
    // fwd 是一个特殊的 Node,没有 key,也没有 val,hash 值为 MOVED,用来标识一个桶已经迁移完毕
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    // 用来控制迁移的进展,如果为 true 说明当前这次循环要干的事情已经完成,可以开始下一个循环
    boolean advance = true;
    // 标示当前线程所有桶的迁移是否完成
    boolean finishing = false;
    // 当前线程需要处理的桶的范围 [nextBound, nextindex)
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        while (advance) {
            int nextIndex, nextBound;
            if (--i >= bound || finishing)
                advance = false;
            // transferIndex <= 0 表示已经迁移完成
            else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
            }
            else if (U.compareAndSetInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
                // 为当前线程分配桶的区间,当前线程需要将负责这个区间内的桶元素迁移到 nextTable 中
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        // 判断当前线程是否完成所有桶的迁移
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            // 如果为 true,说明所有的迁移任务已经完成
            if (finishing) {
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1); // 相当于 n * 0.75
                return;
            }
            // 将参与扩容的线程数量减 1
            if (U.compareAndSetInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                // 如果不相等说明还有其他的线程在参与扩容,当前线程直接退出就行,这行代码与 tryPresize() 中传入的参数有关,第一个进行扩容的线程传入的 sc = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2,所以如果这是最后一个线程,那么 sc - 2 == resizeStamp(n) << RESIZE_STAMP_SHIFT
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                // 最后退出的线程需要再检查一遍容器的状态
                finishing = advance = true;
                i = n;
            }
        }
        // 如果桶中的元素都迁移完成了,则在桶的节点置为 MOVED,表示桶中的元素都迁移完成了
        else if ((f = tabAt(tab, i)) == null)
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            advance = true; // 当前桶已经被处理
        else {
            // 如果上面条件都不满足说明要开始迁移桶中的元素
            synchronized (f) {
             // 省略搬运元素的代码...
            }
        }
    }
}
复制代码

ツリーの操作

タイミングとHashMapのと矛盾木道。試行回数は、単一のバケットリスト要素8ツリー操作よりも大きいが、この時点での容量が少なく全体の容器64よりも、むしろ動作ツリーよりも、拡張のために動作している場合、ツリーはまた、要素を維持されている場合接続関係を維持するために、次のポインタ。

ツリーは、全体のプロセスは、拡張よりもはるかに簡単ですので、それは同期CAS +で行われ、訪れたバケット現在のスレッドを動作させる必要があります。

// ConcurrentHashMap.treeifyBin()
private final void treeifyBin(Node<K,V>[] tab, int index) {
    Node<K,V> b; int n;
    if (tab != null) {
        // 如果容器的容量小于 64,则会进行扩容操作,而不是进行树化操作
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        // 利用 CAS + synchronized 来把链表转成红黑树
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            synchronized (b) {
                if (tabAt(tab, index) == b) {
                    TreeNode<K,V> hd = null, tl = null;
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        TreeNode<K,V> p =
                            new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    // 把转换好的树放到桶上
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}
复制代码

CRUD操作

CASの十分を使用した場合、各バレルの最初の要素を挿入します。バケットの最初の要素を挿入、または削除または更新操作されていない場合は、再び同期使用します。しかし、我々は、ロックオブジェクトは、各要素ごとに作成されていますが、ロックオブジェクトとしてバレル上の最初の要素を使用しないでください。しかし、ロックの唯一の最初の要素は更新前、あなたはまた、我々は再び試みなければならない場合、それは、まだバケット内の最初のノードであることを確認する必要があり、十分ではありません。

GET()操作、他のPUT()に加えて、など、)(同時アクセスに同期CAS +を使用する必要性をオフにします。get操作は、直接、比較的単純であるtabAtのオンラインメソッドを取得します。その他の論理全体の動作は同じです。これは、紹介putVal()置く方法、()、(追加)と、容器内にまたは更新要素法をPutVal()メソッドによって達成される増加などがあります。

// ConcurrentHashMap.putVal()
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // key 和 value 都不允许为 null
    if (key == null || value == null) throw new NullPointerException();
    // 做 hash 运算
    int hash = spread(key.hashCode());
    int binCount = 0;
    // 进入自旋
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        // 如果桶还没有被初始化,则进入初始化(延迟加载)
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 如果这个桶为空,直接使用 CAS 方式来插入元素
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                  
        }
        // 如果发现正在扩容,则参与进扩容,扩容完成之后,通过自旋的方式再次执行插入操作
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // 执行 computeOnlyAbsent 之类的方法
        else if (onlyIfAbsent
                    && fh == hash
                    && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                    && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            // 使用 CAS + synchronized 机制插入元素
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 对现有的键值对进行更新
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                    (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            // 使用尾插法插入新的元素
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    // 如果桶上挂的是树,那就按照树的方法来插入节点
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                        value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    // 如果发现这个节点正在进行 computeIfAbsent 之类的操作,则抛出异常
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                // 检查桶上节点的数量,如果超过 8 了,则尝试进行树化操作
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                // 如果是更新节点操作,那么节点数量就没有增加,直接返回即可
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 用这个方法来检查是否满足扩容的条件,与上面的 helpTransfer 方法不同,addCount 是在键值对插入之后再去检查是否需要扩容
    addCount(1L, binCount);
    return null;
}
复制代码

この方法の他の動作原理は、コンテナ要素をクリア、computを変更する他のすべての同様の要素は、CASによって更新+同期されて削除されます、最後のコールaddcount方法は、カウントを更新し、容量が必要とされているかどうかを決定します。

その他の機能

方法が少し異なるビットサイズを実現しますので、それは、同時であるので、実際の呼び出しのサイズはsumCount方法であります:

//ConcurrentHashMap.sumCount()
final long sumCount() {
    // 统计 cs 和 baseCount 的和
    CounterCell[] cs = counterCells;
    long sum = baseCount;
    if (cs != null) {
        for (CounterCell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}
复制代码

コードの拡張では、コンテナCSとbaseCount統計の数は、実際に使用され、sumCountがロックされていないので、同時の場合には、最初に私は最終的に返されるので、値もある、しかし、最後のCSノートに記録されている参照しますそれは完全に正確ではありません。

コンテナ内の要素ならば、反復変化の過程にあるフェイルセーフメカニズムを使用して、追加のConcurrentHashMapでは、ConcurrentModificationExceptionが例外をスローしません。

最後に、問題イテレータ、KeySetView、ValuesView、EntrySetView三つのクラスについての話は、それぞれのキー、値、およびペアを繰り返すことができます。具体的な実装は比較的簡単であるが、反復プロセスは、並行性制御を追加しないため、最終的な結果は必ずしもトラバースないので、正確です。

オリジナル

関連記事

いいえマイクロチャネル注目しない、話に何か他のもの

おすすめ

転載: juejin.im/post/5e0ff4fce51d4541493621b4