HashMap の非スレッドの安全性

HashMap はスレッドセーフではありません

複数の文書をまとめた学習ノート。

1. スレッドセーフではないのはなぜですか?

1) Rehash 後、循環リンク リスト
HashMap が形成されます。HashMap に要素を追加する際に競合がある場合、ジッパー メソッドを使用して解決します。つまり、同じハッシュコードを持つ 2 つのキーがリンク リストに格納されます。配列内の同じ位置。リンクされたリストの長さが大きすぎる場合、ReSize 操作が実行され、元の配列の要素が新しい配列に再ハッシュされます (ReHash 操作)。[注: サイズ変更のタイミング: HashMap のデフォルトのサイズは 16、loadFactory=0.75 です。つまり、最初の拡張は配列の長さが 16 * 0.75 = 12 より大きいときに発生します。]
JDK1.7では、展開後にリンクリスト内の要素が反転するため、循環リンクリストが生成され、各要素のネクストポインタがnullではなくなり、無限ループが発生します。このとき、CPU 使用率は 100% に近くなります。
JDK1.8 以降、この循環リンク リストの問題は解決されました。改善された方法では、リンク リストを逆にすることはなくなりましたが、元のリンク リストの順序は維持されます。マルチスレッド環境の場合は、最大でも追加されます。さらにいくつかの要素を追加すると、ループはなくなります。
2) データ損失の原因
2 つのスレッドが同時に置かれた場合、2 つのキーのハッシュコードが同じである場合、それらは衝突し、同じリンク リストに追加されます。そうすると、ある値が別の値で上書きされ、データが失われます。

2. スレッドセーフなメソッドにはどのようなものがありますか?

  1. ConcurrentHashMap (推奨)
    Hashtable と比較して、ConcurrentHashMap はアクセスのスレッド安全性を保証するだけでなく、効率も大幅に向上します。ConcurrentHashMap を拡張しても、元のサイズの 2 倍までしか拡張されません。古い配列のデータが新しい配列に移動されると、位置は同じままになるか、インデックス + oldSize に変更され、パラメータ内のノードは展開後にリンク リストの先頭補間メソッドを使用して指定された位置に挿入されます。 。

    JDK1.7のConcurrentHashMapの最下層は配列(セグメント配列+HashEntry配列)+リンクリストで実装されています。Segment 配列の各要素には HashEntry 配列が含まれており、各 HashEntry 配列は複数のリンク リスト構造に属します。セグメント数は初期化すると変更できません。競合を解決するには、ジッパー メソッドを使用します。
    JDK1.7では、ConcurrentHashMapがデータの挿入や読み取りを行う際に、キーに従って対応するセグメントにダイレクトし、そのセグメントのみをロックする、つまりセグメントロック(ロックの範囲を縮小する)を行うため、ロック粒度はJDK1となります。 7 は、セグメント (複数の HashEntry を含む) に基づいており、各セグメントで同時に動作できるスレッドは 1 つだけです。複数のスレッドがコンテナ内の異なるデータ セグメントのデータにアクセスする場合、ロックの競合がなくなり、同時アクセス率が向上します。JDK1.7の最大同時実行数はセグメント数です。

    JDK1.8で採用されているデータ構造はHashMap1.8と同じ、配列+連結リスト/赤黒二分木です。競合するリンク リストの長さが一定の長さに達すると、検索効率を向上させるためにリンク リストが赤黒ツリーに変換されます。競合を解決するには、ジッパー メソッド + 赤黒ツリーを使用します。
    JDK1.8 ConcurrentHashMap 同時実行制御では、Node + CAS + synchronized を使用してスレッドの安全性を確保します。ロックの粒度はより細かくなり、ロックの粒度は HashEntry (各リンク リストの最初のノード) になり、実装の複雑さが軽減されます。同期では、現在のリンク リストまたは赤黒バイナリ ツリーの最初のノードのみがロックされるため、ハッシュが競合しない限り同時実行性は発生せず、他のノードの読み取りと書き込み、および効率には影響しません。大きく改善されるでしょう。JDK 1.8 の最大同時実行性は Node 配列のサイズであり、同時実行性はさらに大きくなります。

  2. Hashtable (使用量は少なく、使用しない方がよい)
    Hashtable は、スレッドの安全性を確保するために、ほとんどのメソッドに synchronized を追加しますが、ほとんどのメソッドが同期されるため、パフォーマンスが低く、現在は非推奨になっています。ハッシュテーブルの最下層は、配列 + リンクされたリストによって実装されます。

  3. synchronizedMap
    Sysnchronized Map は Hashtable に似ていますが、スレッドの安全性を確保するために synchronized も使用します。唯一の違いは、SysnchronizedMap が同期に Object オブジェクトを使用し、それが放棄されていないことです。

おすすめ

転載: blog.csdn.net/weixin_45463572/article/details/130421272