6

マルチスレッドに関する章/本をすべて読めば答えを見つけることができますが、もっと迅速な答えが欲しいです。(私はこのスタックオーバーフローの質問が似ていることを知っていますが、十分ではありません.)

次のクラスがあるとします。

public class TestClass {
   private int someValue;

   public int getSomeValue() { return someValue; }
   public void setSomeValue(int value) {  someValue = value; }
}

このクラスのインスタンスにアクセスする 2 つのスレッド (A と B) があります。次のシーケンスを検討してください。

  1. A: getSomeValue()
  2. B: setSomeValue()
  3. A: getSomeValue()

そうしないと、3 番目のステップで最新の値が返されない可能性があります (A にはキャッシュされた値がある可能性があるため)。これは正しいです?

2 番目のシナリオ:

  1. B: setSomeValue()
  2. A: getSomeValue()

この場合、A は常に正しい値を取得します。これは、これが最初のアクセスであり、キャッシュされた値をまだ取得できないためです。これは正しいですか?

クラスが2番目の方法でのみアクセスされる場合、揮発性/同期は必要ありませんか?

この例は単純化されたものであり、実際には複雑なクラスの特定のメンバー変数とメソッドについて疑問に思っていることに注意してください。クラス全体についてではありません (つまり、どの変数を揮発性にするか、同期アクセスにする必要があるか)。要点は次のとおりです。より多くのスレッドが特定のデータにアクセスする場合、必ず同期アクセスが必要ですか、それともアクセス方法 (順序など) に依存するのでしょうか?


コメントを読んだ後、別の例で混乱の原因を提示しようとしました。

  1. UI スレッドから:threadA.start()
  2. threadA が を呼び出しgetSomeValue()、UI スレッドに通知します。
  3. UI スレッドはメッセージを (メッセージ キューで) 取得するため、以下を呼び出します。threadB.start()
  4. threadB が を呼び出しsetSomeValue()、UI スレッドに通知します。
  5. UI スレッドはメッセージを取得し、threadA に通知します (何らかの方法で、たとえばメッセージ キュー)。
  6. threadA 呼び出しgetSomeValue()

これは完全に同期された構造ですが、なぜこれは、threadA がステップ 6 で最新の値を取得することを意味するのでしょうか? (someValue揮発性でない場合、またはどこからでもアクセスしたときにモニターに入れられない場合)

4

5 に答える 5

3

2 つのスレッドが同じメソッドを呼び出している場合、そのメソッドが呼び出される順序について保証することはできません。したがって、呼び出し順序に依存する元の前提は無効です。

メソッドが呼び出される順序についてではありません。同期についてです。何らかのメカニズムを使用して、一方のスレッドを待機させ、もう一方のスレッドが書き込み操作を完全に完了することについてです。 複数のスレッドを持つことを決定したら、 その同期メカニズムを提供して、データの破損を回避する必要があります。

于 2012-06-29T22:35:48.290 に答える
2

本を見てみましょう

フィールドは volatile と宣言される場合があります。その場合、Java メモリ モデル (§17) により、すべてのスレッドが変数に対して一貫した値を参照することが保証されます。

そのvolatileため、宣言された変数がスレッド ローカル ストレージにコピーされないことが保証されます。それ以外の場合は許可されます。これは、共有ストレージへの非常に単純な種類の同期アクセスのためのロックの意図的な代替手段であるとさらに説明されています。

また、アクセスは必然的にアトミックであることを説明しているこの以前の記事も参照してください (ただし、 orではありません)。intdoublelong

これらは一緒に、intフィールドが宣言されている場合、volatile原子性を保証するためにロックが必要ないことを意味します: 半分完全な書き込みから生じる混乱した値ではなく、メモリ位置に最後に書き込まれた値が常に表示されます (double で可能なように)または長い)。

ただし、ゲッターとセッター自体がアトミックであることを暗示しているようです。これは保証されません。JVM は、呼び出しまたはリターン シーケンスの途中で実行を中断できます。この例では、これによる影響はありません。しかし、呼び出しに副作用がある場合、たとえばsetSomeValue(++val)、別の話になります。

于 2012-06-30T03:41:51.163 に答える
2

誰もが知っているように、保護する必要があるのはデータの重要な状態であり、データの重要な状態を管理するアトミック ステートメントは同期する必要があります。

この例では、揮発性が使用され、カウンターの値を 10000 まで毎回 1 ずつインクリメントする 2 つのスレッドを使用しました。したがって、合計は 20000 である必要があります。

次に、同期キーワードを使用して機能させました。

同期化により、スレッドが同期化されたメソッドにアクセスしているときに、他のスレッドがこのオブジェクトまたはそのオブジェクトの他の同期化されたメソッドにアクセスできなくなり、データの破損が行われないことが保証されます。

スレッド セーフ クラスとは、そのクラスにアクセスするクライアント側からのスレッド セーフ メカニズムなしで、下線部のランタイム環境のスケジューリングとインターリーブが存在する場合でも、その正確性を維持することを意味します。

于 2012-06-30T03:06:22.847 に答える
1

そうしないと、3 番目のステップで最新の値が返されない可能性があります (A にはキャッシュされた値がある可能性があるため)。これは正しいです?

スレッド B が setSomeValue() を呼び出す場合、スレッド A がその値を読み取れるようにするには、何らかの同期が必要です。volatile単独でこれを達成することはできず、メソッドを同期させることもできません。これを行うコードは、最終的には、追加した同期コードでA: getSomeValue()あり、B: setSomeValue(). あなたが示唆するように、スレッドを同期するためにメッセージキューを使用した場合、これは、スレッド B がメッセージキューのロックを取得すると、スレッド A によって行われたメモリの変更がスレッド B に表示されるために発生します。

クラスが2番目の方法でのみアクセスされる場合、揮発性/同期は必要ありませんか?

本当に独自の同期を行っている場合は、これらのクラスがスレッドセーフかどうかを気にしているようには見えません。ただし、同時に複数のスレッドからアクセスしていないことを確認してください。そうしないと、アトミックでない (int を代入する) メソッドによって、予測不能な状態になる可能性があります。一般的なパターンの 1 つは、受信スレッドがセッターを呼び出していないことを確認できるように、共有状態を不変オブジェクトに入れることです。

更新して複数のスレッドから読み取りたいクラスがある場合は、おそらく最も簡単な方法から始めます。これは多くの場合、すべてのパブリック メソッドを同期することです。これがボトルネックであると本当に信じている場合は、Java のより複雑なロック メカニズムを調べることができます。

それでは、volatile は何を保証するのでしょうか?

正確なセマンティクスについては、チュートリアルを読む必要があるかもしれませんが、要約する 1 つの方法は、1) volatile にアクセスする最後のスレッドによって行われたメモリ変更は、volatile にアクセスしている現在のスレッドに表示され、2) ということです。 volatile へのアクセスはアトミックです (部分的に構築されたオブジェクト、または部分的に割り当てられた double または long にはなりません)。

同期ブロックには、類似のプロパティがあります。1) ロックにアクセスする最後のスレッドによって行われたメモリ変更は、このスレッドに表示されます。2) ブロック内で行われた変更は、他の同期ブロックに対してアトミックに実行されます。

(1) volatile への変更 (JDK 1.5 以降の話です) や同期ブロック内での変更だけでなく、メモリの変更を意味します。これは、人々が順序付けについて言及するときに意味するものであり、これはさまざまなチップ アーキテクチャでさまざまな方法で達成されます。多くの場合、メモリ バリアを使用します。

また、同期ブロックの場合 (2) は、同じロックで同期された別のブロック内にいる場合に矛盾した値が表示されないことを保証するだけです。自分が何をしているのか本当にわかっていない限り、通常は共有変数へのすべてのアクセスを同期することをお勧めします。

于 2012-06-30T04:17:42.833 に答える
1

問題は、Java が単なる仕様であることです。多くの JVM 実装と物理動作環境の例があります。任意の組み合わせで、アクションは安全または安全でない可能性があります。たとえば、シングル プロセッサ システムでは、例の volatile キーワードはおそらく完全に不要です。メモリと言語の仕様の作成者は、考えられる動作条件のセットを合理的に説明できないため、すべての準拠実装で動作することが保証されている特定のパターンをホワイトリストに登録することを選択します。これらのガイドラインに従うことで、コードがターゲット システムで動作し、適度に移植できるようになります。

この場合、「キャッシング」は通常、ハードウェア レベルで行われているアクティビティを指します。Java で発生する特定のイベントにより、マルチ プロセッサ システムのコアがキャッシュを「同期」します。volatile 変数へのアクセスはこの例であり、同期ブロックは別の例です。これら 2 つのスレッド X と Y が異なるプロセッサで実行されるようにスケジュールされているシナリオを想像してください。

X starts and is scheduled on proc 1
y starts and is scheduled on proc 2

.. now you have two threads executing simultaneously
to speed things up the processors check local caches
before going to main memory because its expensive.

x calls setSomeValue('x-value') //assuming proc 1's cache is empty the cache is set
                                //this value is dropped on the bus to be flushed
                                //to main memory
                                //now all get's will retrieve from cache instead
                                //of engaging the memory bus to go to main memory 
y calls setSomeValue('y-value') //same thing happens for proc 2

//Now in this situation depending on to order in which things are scheduled and
//what thread you are calling from calls to getSomeValue() may return 'x-value' or
//'y-value. The results are completely unpredictable.  

ポイントは、volatile(準拠した実装では) 順序付けられた書き込みが常にメイン メモリにフラッシュされ、他のプロセッサのキャッシュが次のアクセスの前に「ダーティ」としてフラグ付けされることを保証することです。

免責事項: volatile はロックされません。これは、特に次の場合に重要です。

volatile int counter;

public incrementSomeValue(){
    counter++; // Bad thread juju - this is at least three instructions 
               // read - increment - write             
               // there is no guarantee that this operation is atomic
}

setSomeValueあなたの意図が常に前に呼び出されなければならない場合、これはあなたの質問に関連している可能性がありますgetSomeValue

getSomeValue()の最新の呼び出しを常に反映する必要があるという意図がある場合、これはキーワードsetSomeValue()の使用に適した場所です。それがなければ、最初にスケジュールされたとしても、最新の呼び出しに反映されるvolatile保証はないことに注意してください。getSomeValue()setSomeValue()setSomeValue()

于 2012-06-29T23:06:37.293 に答える