web-dev-qa-db-ja.com

ConcurrentHashMap計算メソッドのコードを理解する

ConcurrentHashMap計算メソッドで次の奇妙なコードを見つけました:(1847行目)

public V compute(K key,
                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    ...
    Node<K,V> r = new ReservationNode<K,V>();
    synchronized (r) {   <--- what is this?
        if (casTabAt(tab, i, null, r)) {
            binCount = 1;
            Node<K,V> node = null;

したがって、コードは、現在のスレッドでのみ使用可能な新しい変数で同期を実行します。つまり、このロックを獲得したり、メモリバリア効果を引き起こしたりするスレッドは他にありません。

このアクションのポイントは何ですか?それは間違いですか、それとも私が知らないいくつかの明白でない副作用を引き起こしますか?

追伸jdk1.8.0_131

46
AdamSkywalker
casTabAt(tab, i, null, r)

rへの参照を公開しています。

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

ctabに入れられているため、別のスレッドによってアクセスされる可能性があります。 in putVal 。そのため、このsynchronizedブロックは、他のスレッドがそのNodeを使用して他の同期処理を行うことを除外するために必要です。

39
Andy Turner

rはその時点では新しい変数ですが、if (casTabAt(tab, i, null, r))を介してすぐに内部tableに入れられます。この時点で、別のスレッドがコード。

このように内部の非javadocコメントはそれを説明しています

空のビンへの最初のノードの(putまたはそのバリアントを介した)挿入は、それをビンにCASするだけで実行されます。これは、ほとんどのキー/ハッシュ配布の下でput操作を行う最も一般的なケースです。他の更新操作(挿入、削除、および置換)にはロックが必要です。個別のロックオブジェクトを各ビンに関連付けるために必要なスペースを無駄にしたくないため、代わりにビンリスト自体の最初のノードをロックとして使用します。これらのロックのロックサポートは、組み込みの「同期」モニターに依存しています。

16
Kayaman

ここでわずか0.02 $

そこに示しているのは、実際にはReservationNodeだけです。つまり、ビンが空であり、いくつかのNodeの予約が作成されています。このメソッドは、後でこのNodeを実際のものに置き換えます。

 setTabAt(tab, i, node);

私が理解している限り、これは置換がアトミックになるために行われます。一度casTabAtを介して公開され、他のスレッドがそれを見ると、ロックが既に保持されているため、同期できません。

また、ビンにエントリがある場合、最初にNodeを使用して同期します(メソッドのさらに下にあります):

boolean added = false;
            synchronized (f) { // locks the bin on the first Node
                if (tabAt(tab, i) == f) {
......

サイドノードとして、9以降、このメソッドは8で変更されました。たとえば、次のコードを実行します。

 map.computeIfAbsent("KEY", s -> {
    map.computeIfAbsent("KEY"), s -> {
        return 2;
    }
 })

8では終わりませんが、9ではRecursive Updateをスローします。

3
Eugene