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
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);
}
c
はtab
に入れられているため、別のスレッドによってアクセスされる可能性があります。 in putVal
。そのため、このsynchronized
ブロックは、他のスレッドがそのNode
を使用して他の同期処理を行うことを除外するために必要です。
r
はその時点では新しい変数ですが、if (casTabAt(tab, i, null, r))
を介してすぐに内部table
に入れられます。この時点で、別のスレッドがコード。
このように内部の非javadocコメントはそれを説明しています
空のビンへの最初のノードの(putまたはそのバリアントを介した)挿入は、それをビンにCASするだけで実行されます。これは、ほとんどのキー/ハッシュ配布の下でput操作を行う最も一般的なケースです。他の更新操作(挿入、削除、および置換)にはロックが必要です。個別のロックオブジェクトを各ビンに関連付けるために必要なスペースを無駄にしたくないため、代わりにビンリスト自体の最初のノードをロックとして使用します。これらのロックのロックサポートは、組み込みの「同期」モニターに依存しています。
ここでわずか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
をスローします。