58

これは、に関するJavaDocからの一節ConcurrentHashMapです。通常、取得操作はブロックされないため、更新操作と重複する可能性があるとのことです。get()これは、メソッドがスレッドセーフではないことを意味しますか?

「ただし、すべての操作がスレッドセーフであっても、取得操作はロックを必要とせず、すべてのアクセスを妨げる方法でテーブル全体をロックすることはサポートされていません。このクラスは、依存するプログラムでHashtableと完全に相互運用できます。スレッドセーフですが、同期の詳細にはありません。

通常、取得操作(getを含む)はブロックされないため、更新操作(putおよびremoveを含む)と重複する場合があります。取得は、その開始を保持している最近完了した更新操作の結果を反映しています。」

4

6 に答える 6

67

メソッドはスレッドセーフであり、他のget()ユーザーがこの特定の問題に関して役立つ回答を提供してくれました。

ただし、ConcurrentHashMapは のスレッドセーフなドロップイン置換HashMapですが、複数の操作を行う場合、コードを大幅に変更する必要がある場合があることを認識することが重要です。たとえば、次のコードを見てください。

if (!map.containsKey(key)) 
   return map.put(key, value);
else
   return map.get(key);

マルチスレッド環境では、これは競合状態です。を使用ConcurrentHashMap.putIfAbsent(K key, V value)し、戻り値に注意を払う必要があります。これは、プット操作が成功したかどうかを示します。詳細については、ドキュメントを参照してください。


これが競合状態である理由の説明を求めるコメントへの回答。

マップに 2 つの異なる値を配置し、それぞれ同じキーを持つ2 つのスレッドがAあるとします。キーは最初はマップに存在しません。これらは次のようにインターリーブします。Bv1v2

  • スレッドAが呼び出さcontainsKeyれ、キーが存在しないことがわかりますが、すぐに中断されます。
  • スレッドBが呼び出さcontainsKeyれ、キーが存在しないことがわかり、その値を挿入する時間がありますv2
  • スレッドAは再開して挿入し、スレッドによって挿入された値v1を「平和的に」上書きします (スレッドセーフであるため) 。putB

現在、スレッドBは独自の値 を正常に挿入したと「考え」ていますv2が、マップには が含まれていますv1。スレッドBが呼び出しv2.updateSomething()て、マップの消費者 (たとえば、他のスレッド) がそのオブジェクトにアクセスできると「考え」、おそらく重要な更新 (「このビジターの IP アドレスがDOS、今後はすべてのリクエストを拒否します」)。代わりに、オブジェクトはすぐにガベージ コレクションされて失われます。

于 2013-02-19T00:33:18.657 に答える
20

スレッドセーフです。ただし、スレッドセーフである方法は、期待したものとは異なる場合があります。あなたが見ることができるいくつかの「ヒント」があります:

Hashtableこのクラスは、スレッド セーフに依存しているが同期の詳細には依存していないプログラムと完全に相互運用可能です。

全体像をより完全に把握するには、ConcurrentMapインターフェースを意識する必要があります。

オリジナルMapは、いくつかの非常に基本的な読み取り/更新メソッドを提供します。私でさえ、Map;のスレッドセーフな実装を作成できました。私の同期メカニズムを考慮しないと、人々が私のマップを使用できない場合がたくさんあります。これは典型的な例です:

if (!threadSafeMap.containsKey(key)) {
   threadSafeMap.put(key, value);
}

マップ自体はスレッドセーフですが、このコードはスレッドセーフではありません。containsKey()同時に呼び出す 2 つのスレッドは、そのようなキーがないと判断する可能性があるため、両方とも に挿入しMapます。

この問題を解決するには、追加の同期を明示的に行う必要があります。私のマップのスレッドセーフが同期されたキーワードによって達成されると仮定すると、次のことを行う必要があります。

synchronized(threadSafeMap) {
    if (!threadSafeMap.containsKey(key)) {
       threadSafeMap.put(key, value);
    }
}

このような追加コードでは、マップの「同期の詳細」について知っておく必要があります。上記の例では、同期が「同期」によって達成されることを知る必要があります。

ConcurrentMapインターフェイスはこれをさらに一歩進めます。マップへの複数のアクセスを含む、いくつかの一般的な「複雑な」アクションを定義します。たとえば、上記の例は として公開されputIfAbsent()ます。これらの「複雑な」アクションにより、ConcurrentMap(ほとんどの場合) のユーザーは、アクションをマップへの複数のアクセスと同期させる必要がなくなります。したがって、Map の実装では、パフォーマンスを向上させるために、より複雑な同期メカニズムを実行できます。 ConcurrentHashhMapは良い例です。実際、スレッドセーフは、マップの異なるパーティションに対して個別のロックを維持することによって維持されます。マップへの同時アクセスによって内部データ構造が破損したり、更新が予期せず失われたりすることがないため、スレッドセーフです。

上記のすべてを念頭に置くと、Javadoc の意味がより明確になります。

ConcurrentHashMapスレッドセーフのために「同期」を使用していないため、「取得操作(取得を含む)は通常ブロックされません」 。それ自体のロジックがgetスレッドセーフを処理します。さらに Javadoc を調べると、次のようになります。

テーブルは内部的に分割され、指定された数の同時更新を競合なしで許可しようとします

検索がノンブロッキングであるだけでなく、更新も同時に発生する可能性があります。ただし、非ブロッキング/同時更新は、スレッドが安全でないことを意味するものではありません。これは単に、スレッドセーフのために単純な「同期」以外の方法を使用していることを意味します。

ただし、内部の同期メカニズムは公開されていないため、 が提供するもの以外の複雑なアクションを実行するConcurrentMap場合は、ロジックの変更を検討するか、 を使用しないことを検討する必要がありますConcurrentHashMap。例えば:

// only remove if both key1 and key2 exists
if (map.containsKey(key1) && map.containsKey(key2)) {
    map.remove(key1);
    map.remove(key2);
}
于 2013-02-19T02:41:02.330 に答える
10

ConcurrentHashmap.get()スレッドセーフであるという意味で

  • 以下を含む例外はスローされませんConcurrentModificationException
  • 過去のある(最近の)時点で真だった結果を返します。これは、getへの2つの連続した呼び出しが異なる結果を返す可能性があることを意味します。もちろん、これは他のすべてにも当てはまりMapます。
于 2013-02-19T00:26:48.857 に答える
8

HashMapに基づいて「バケット」に分割されhashCodeます。ConcurrentHashMapこの事実を利用しています。その同期メカニズムは、全体ではなくバケットのブロックに基づいていますMap。このようにして、少数のスレッドが少数の異なるバケットに同時に書き込むことができます (1 つのスレッドが一度に 1 つのバケットに書き込むことができます)。

からの読み取りは、ConcurrentHashMap ほとんど同期を使用しません。同期は、キーの値をフェッチしているときに値が表示されるときに使用されnull ますConcurrentHashMap値として保存できないためnull(はい、キーは別として、値もnulls にすることはできません)、読み取り中のフェッチが、別のスレッドによるnullマップエントリ(キーと値のペア) の初期化中に発生したことを示唆しています: キーが割り当てられたとき、しかし値はまだありません、そしてそれはまだデフォルトのnullを保持しています。
このような場合、読み取りスレッドは、エントリが完全に書き込まれるまで待機する必要があります。

そのため、結果read()はマップの現在の状態に基づいています。更新途中のキーの値を読み取ると、書き込み処理がまだ完了していないため、古い値を取得する可能性があります。

于 2013-02-19T00:54:03.147 に答える
5

ConcurrentHashMap の get() は、揮発性の値を読み取るため、スレッドセーフです。また、いずれかのキーの値が null の場合、get() メソッドはロックを取得するまで待機してから、更新された値を読み取ります。

メソッドが CHM を更新する場合put()、そのキーの値を null に設定し、新しいエントリを作成して CHM を更新します。この null 値はget()、別のスレッドが同じキーで CHM を更新していることを示すシグナルとしてメソッドによって使用されます。

于 2013-08-20T05:22:26.920 に答える
4

これは、1 つのスレッドが更新中で 1 つのスレッドが読み取りを行っているときに、ConcurrentHashMap メソッドを最初に呼び出したスレッドが最初に操作を行うという保証がないことを意味します。

ボブがどこにいるかを示すアイテムの更新について考えてみてください。あるスレッドが Bob の居場所を尋ねるのとほぼ同時に、別のスレッドが更新されて彼が「内部」に来たと報告された場合、リーダー スレッドが Bob のステータスを「内部」と「外部」のどちらとして取得するかを予測することはできません。更新スレッドが最初にメソッドを呼び出したとしても、リーダー スレッドは「外部」ステータスを取得する可能性があります。

スレッドが互いに問題を引き起こすことはありません。コードはスレッドセーフです。

1 つのスレッドは、無限ループに入ったり、おかしな NullPointerExceptions の生成を開始したり、古いステータスの半分と新しいステータスの半分で "その側" を取得したりすることはありません。

于 2013-02-19T00:30:35.053 に答える