9

ConcurrentHashMap の形式のオブジェクト ストアを持つ既存のコードを使用しています。マップ内には、複数のスレッドで使用される変更可能なオブジェクトが格納されています。設計上、2 つのスレッドが同時にオブジェクトを変更しようとすることはありません。私の懸念は、スレッド間の変更の可視性に関するものです。

現在、オブジェクトのコードは「セッター」で同期されています (オブジェクト自体によって保護されています)。「ゲッター」には同期がなく、メンバーは揮発性ではありません。これは、私にとって、可視性が保証されていないことを意味します。ただし、オブジェクトが変更されると、マップに再配置put()されます (同じキーでメソッドが再度呼び出されます)。これは、別のスレッドがマップからオブジェクトをプルすると、変更が表示されるということですか?

これについては、stackoverflow、JCIP、および java.util.concurrent のパッケージの説明で調査しました。私は基本的に自分自身を混乱させたと思います...しかし、この質問をするようになった最後のストローは、パッケージの説明からのものでした。

オブジェクトを並行コレクションに配置する前のスレッド内のアクションは、別のスレッド内のコレクションからのその要素へのアクセスまたは削除に続くアクションの前に発生します。

私の質問に関連して、「アクション」には、re-put() の前にマップに格納されたオブジェクトへの変更が含まれますか? これらすべてがスレッド間の可視性につながる場合、これは効率的なアプローチですか? 私はスレッドに比較的慣れていないので、コメントをいただければ幸いです。

編集:

皆様、ご回答ありがとうございます!これは StackOverflow に関する私の最初の質問であり、非常に役に立ちました。

それが私の混乱を最も明確に解決したと思うので、私はptomliの答えに行かなければなりません。つまり、この場合、「前に発生」関係を確立しても、必ずしも変更の可視性に影響するわけではありません。私の「タイトルの質問」は、テキストで説明されている実際の質問に関して構成が不十分です。ptomliの答えは、私がJCIPで読んだものと一致するようになりました。オブジェクトをマップに再配置しても、挿入されたオブジェクトのメンバーを変更するためのこの共通ロックは提供されません。

変更に関するすべてのヒント (不変オブジェクトなど) に感謝し、心から同意します。しかし、この場合、前述したように、慎重にスレッドを処理するため、同時変更はありません。1 つのスレッドがオブジェクトを変更し、後で別のスレッドがそのオブジェクトを読み取ります (CHM がオブジェクト コンベアです)。私が提供した状況を考えると、後で実行するスレッドが最初から変更を確認できるようにするには、CHM が不十分だと思います。しかし、タイトルの質問に正解した方も多いと思います。

4

6 に答える 6

7

concurrHashMap.putオブジェクトに書き込むたびに呼び出します。concurrHashMap.getただし、各読み取りの前に呼び出すことも指定していません。これは必要です。

これはすべての形式の同期に当てはまります。両方のスレッドにいくつかの「チェックポイント」が必要です。1 つのスレッドだけを同期しても意味がありません。

ConcurrentHashMap のソース コードを確認して、事前発生putgetトリガーすることは確認していませんが、そうすべきであることは論理的です。

putただし、 と の両方を使用しても、メソッドにはまだ問題がありますget。この問題は、オブジェクトを変更し、それが .NET になる前に他のスレッドによって (一貫性のない状態で) 使用された場合に発生しますput。古い値はまだ読み取られていないため読み取られ、問題は発生しないと考えられるため、これは微妙な問題ですput。問題は、同期しない場合、一貫性のある古いオブジェクトを取得することが保証されず、動作が未定義になることです。JVM は、他のスレッド内のオブジェクトのどの部分でも、いつでも更新できます。スレッド間で一貫した方法で値を更新していることを確認できるのは、明示的な同期を使用する場合のみです。

できること:
(1) コード内のすべてのオブジェクトへのすべてのアクセス (ゲッターとセッター) を同期します。セッターには注意してください。オブジェクトを矛盾した状態に設定できないようにしてください。たとえば、名と姓を設定する場合、2 つの同期セッターを使用するだけでは十分ではありません。両方の操作のオブジェクト ロックを一緒に取得する必要があります。
または
(2)putマップ内のオブジェクトの場合、オブジェクト自体の代わりにディープコピーを配置します。そうすれば、他のスレッドが一貫性​​のない状態でオブジェクトを読み取ることはありません。

編集
私はちょうど気づいた

現在、オブジェクトのコードは「セッター」で同期されています (オブジェクト自体によって保護されています)。「ゲッター」には同期がなく、メンバーは揮発性ではありません。

これは良くない。上で述べたように、1 つのスレッドのみで同期することは、まったく同期ではありません。すべてのライター スレッドで同期することもできますが、リーダーが正しい値を取得できないため、誰が気にしますか。

于 2011-10-18T15:11:08.097 に答える
5

これはすでにいくつかの回答で述べられていると思いますが、要約すると

あなたのコードがうまくいくなら

  • CHM#get
  • さまざまなセッターを呼び出す
  • CHM#プット

次に、put によって提供される「happens-before」により、すべての mutate 呼び出しが put の前に実行されることが保証されます。これは、後続の get がそれらの変更を確認することが保証されることを意味します。

あなたの問題は、イベントの実際の流れが

  • スレッド 1: CHM#get
  • スレッド 1: セッターを呼び出す
  • スレッド 2: CHM#get
  • スレッド 1: セッターを呼び出す
  • スレッド 1: セッターを呼び出す
  • スレッド 1: CHM#put

その場合、オブジェクトの状態がスレッド 2 でどうなるかについての保証はありません。最初のセッターによって提供された値を持つオブジェクトが表示される場合と、そうでない場合があります。

不変のコピーは、完全に一貫したオブジェクトのみが発行されるため、最良のアプローチです。さまざまなセッターを同期 (または基になる参照を揮発性) にしても、一貫した状態を公開することはできません。これは、オブジェクトが各呼び出しで各ゲッターの最新の値を常に参照することを意味するだけです。

于 2011-10-18T16:37:14.783 に答える
4

あなたの質問は、同時マップ自体よりも、マップに格納しているオブジェクトと、それらが同時アクセスにどのように反応するかに関連していると思います。

マップに保存しているインスタンスに同期されたミューテーターがあり、同期されたアクセサーがない場合、説明されているようにそれらをスレッドセーフにする方法がわかりません。

等式からMap外して、格納しているインスタンス自体がスレッド セーフかどうかを判断します。

ただし、オブジェクトが変更されると、マップに再配置されます (同じキーで put() メソッドが再度呼び出されます)。これは、別のスレッドがマップからオブジェクトをプルすると、変更が表示されるということですか?

これは混乱を示しています。Map に再配置されたインスタンスは、別のスレッドによって Map から取得されます。これが並行マップの保証です。これは、格納されたインスタンス自体の状態の可視性とは関係ありません。

于 2011-10-18T14:51:35.590 に答える
3

私の理解では、再配置後のすべての取得で機能するはずですが、これは非常に危険な同期方法です。

何が起こるかは、再配置の前に起こりますが、変更が行われている間です。一部の変更しか表示されず、オブジェクトの状態に一貫性がなくなる可能性があります。

可能であれば、不変オブジェクトをマップに保存することをお勧めします。その後、すべての get は、get を実行した時点で最新だったオブジェクトのバージョンを取得します。

于 2011-10-18T15:02:40.300 に答える
1

はい、putキー値がマップに既に存在する場合でも、揮発性の書き込みが発生します。

ConcurrentHashMap を使用してスレッド間でオブジェクトを公開すると、非常に効率的です。マップに追加したオブジェクトは、それ以上変更しないでください。(厳密に不変である必要はありません(最終フィールドを使用))

于 2011-10-18T15:26:28.853 に答える