Java Concurrency in Practice、Chapter 11.4.3によると:
ロック分割は、変数化された独立したオブジェクトのセットのパーティションロックに拡張できる場合があります。その場合、ロックストライピングと呼ばれます。たとえば、ConcurrentHashMapの実装では、16個のロックの配列を使用します。各ロックは、ハッシュバケットの1/16を保護します。バケットNはロックNmod16によって保護されています。
ロックストライピングとバケットのメカニズムを理解して視覚化するのにまだ問題があります。誰かがこれをよく理解した言葉で説明できますか:)
前もって感謝します。
ハッシュマップは配列上に構築され、ハッシュ関数はオブジェクトを基になる配列の要素にマップします。基になる配列に1024個の要素があるとしましょう-ConcurrentHashMapは、実際にはこれを64個の要素からなる16個の異なるサブ配列に変換します。 {0、63}、{64、127}など。各サブ配列には独自のロックがあるため、{0、63}サブ配列を変更しても{64、127}サブ配列に影響はありません-1つのスレッド別のスレッドが2番目のサブ配列に書き込む間、最初のサブ配列に書き込むことができます。
Collections.synchronizedMap()
とConcurrentHashMap
のロックの違いは次のとおりです。
複数のスレッドがCollections.synchronizedMap()
に頻繁にアクセスする場合、各メソッドは共有ロックを使用して同期されるため、多くの競合が発生します(つまり、スレッドXがCollections.synchronizedMap()
のメソッドを呼び出す場合、すべて他のスレッドは、スレッドXが呼び出したメソッドから戻るまで、Collections.synchronizedMap()
上のメソッドの呼び出しをブロックされます。
ConcurrentHashMap
には可変数のロック(デフォルトは16)があり、それぞれがConcurrentHashMap
内のキーのセグメントを保護します。したがって、160個のキーを持つConcurrentHashMap
の場合、各ロックは10個の要素を保護します。したがって、キーを操作するメソッド(get
、put
、set
など)は、キーが存在するキーを操作する他のメソッドへのアクセスのみをロックアウトします。同じセグメント。たとえば、スレッドXがput(0, someObject)
を呼び出し、次にスレッドYがput(10, someOtherObject)
を呼び出す場合、それらの呼び出しは同時に実行でき、スレッドYはスレッドXがput(0, someObject)
。以下に例を示します。
さらに、size()
やisEmpty()
などの特定のメソッドはまったく保護されていません。これにより同時実行性が向上しますが、一貫性が強くないことを意味します(同時に変化する状態を反映しません)。
public static void main(String[] args) {
ConcurrentHashMap<Integer, Object> map = new ConcurrentHashMap<>(160);
new Thread(new Runnable() {
@Override
public void run() {
map.put(0, "guarded by one lock");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
map.put(10, "guarded by another lock");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
// could print 0, 1, or 2
System.out.println(map.count());
}
}.start();
}
ここでの重要な概念は「バケット」です。代わりに、このハッシュテーブル全体にグローバルロックを使用し、バケットごとに1つの小さなロックを使用します。また、バケットソートによく似ており、ソートの複雑さを改善できます。