HashMapにアクセスする2つの複数のスレッドがあるが、それらが同時に同じキーにアクセスしないことを保証する場合、それでも競合状態につながる可能性がありますか?
回答:
@dotsidの回答で、彼は次のように述べています。
何らかの方法でHashMapを変更すると、コードが壊れてしまいます。
彼は正しい。同期なしで更新されたHashMapは、スレッドが互いに素なキーのセットを使用している場合でも壊れます。うまくいかないことがいくつかあります。
あるスレッドがを実行するput
場合、別のスレッドはハッシュマップのサイズの古い値を見る可能性があります。
スレッドがput
テーブルの再構築をトリガーするaを実行すると、別のスレッドがハッシュテーブル配列参照、そのサイズ、その内容、またはハッシュチェーンの一時的または古いバージョンを確認する場合があります。混沌が続く可能性があります。
スレッドがput
他のスレッドで使用されているキーと衝突するキーに対してaを実行し、後者のスレッドがput
そのキーに対してaを実行すると、後者はハッシュチェーン参照の古いコピーを見る可能性があります。混沌が続く可能性があります。
あるスレッドが他のスレッドのキーの1つと衝突するキーでテーブルをプローブすると、チェーン上でそのキーに遭遇する可能性があります。そのキーでequalsを呼び出し、スレッドが同期されていない場合、equalsメソッドはそのキーで古い状態に遭遇する可能性があります。
また、2つのスレッドが同時に実行put
またはremove
要求している場合、競合状態になる可能性が多数あります。
私は3つの解決策を考えることができます:
ConcurrentHashMap
ます。HashMap
が、外部で同期します。たとえば、プリミティブミューテックス、Lock
オブジェクトなどを使用します。HashMap
スレッドごとに異なるものを使用してください。スレッドに実際に互いに素なキーのセットがある場合は、(アルゴリズムの観点から)単一のマップを共有する必要はありません。実際、アルゴリズムに、ある時点でマップのキー、値、またはエントリを反復するスレッドが含まれる場合、単一のマップを複数のマップに分割すると、処理のその部分が大幅に高速化される可能性があります。ConcurrentHashMapを使用するだけです。ConcurrentHashMapは、ハッシュバケットの範囲をカバーする複数のロックを使用して、ロックが競合する可能性を減らします。競合していないロックを取得すると、パフォーマンスにわずかな影響があります。
元の質問に答えるには:javadocによると、マップの構造が変更されない限り、問題はありません。これは、要素をまったく削除せず、マップにまだ存在しない新しいキーを追加しないことを意味します。既存のキーに関連付けられている値を置き換えることは問題ありません。
複数のスレッドが同時にハッシュマップにアクセスし、少なくとも1つのスレッドがマップを構造的に変更する場合は、外部で同期する必要があります。(構造変更とは、1つ以上のマッピングを追加または削除する操作です。インスタンスに既に含まれているキーに関連付けられた値を変更するだけでは、構造変更にはなりません。)
視認性を保証するものではありませんが。したがって、古い関連付けの取得をときどき受け入れる必要があります。
それはあなたが「アクセスする」の下で何を意味するかによります。読んでいるだけなら、「起こる前に」ルールで保証されたデータの可視性があれば、同じキーでも読むことができます。これは、HashMap
変更してはならず、すべての変更(初期構築)は、リーダーがにアクセスし始める前に完了する必要があることを意味しHashMap
ます。
HashMap
何らかの方法でを変更すると、コードは単に壊れます。@Stephen Cは、その理由を非常によく説明しています。
編集:最初のケースが実際の状況である場合はCollections.unmodifiableMap()
、HashMapが決して変更されないことを確認するために使用することをお勧めします。が指すオブジェクトHashMap
も変更しないでfinal
ください。キーワードを積極的に使用すると役立ちます。
そして、@ Lars Andrenが言うように、ConcurrentHashMap
ほとんどの場合、最良の選択です。
unmodifiableMap
クライアントがマップを変更できないようにします。基になるマップが変更されていないことを保証するものではありません。
2つのスレッドから適切に同期せずにHashMapを変更すると、競合状態が発生しやすくなります。
put()
が内部テーブルのサイズ変更につながる場合、これには時間がかかり、他のスレッドは古いテーブルへの書き込みを続行します。put()
キーのハッシュコードがテーブルサイズを法として等しい場合、異なるキーの2つは同じバケットの更新につながります。(実際には、ハッシュコードとバケットインデックスの関係はより複雑ですが、それでも衝突が発生する可能性があります。)HashMap
実装の内部によっては、HashMap
メモリの異常によってデータ構造などが破損する可能性があります。