スレッド間で共有されるオブジェクトがある場合、すべてのフィールドはfinal
またはvolatile
のいずれかであるように思えますが、次の理由があります。
フィールドを変更する必要がある場合(別のオブジェクトをポイントし、プリミティブ値を更新する)、フィールドはvolatile
にして、他のすべてのスレッドが新しい値で動作するようにします。キャッシュされた値を返す可能性があるため、このフィールドにアクセスするメソッドの同期だけでは不十分です。
フィールドを変更しない場合は、final
にします。
しかし、私はこれについて何も見つけることができなかったので、この論理に欠陥があるのか、それともあまりにも明白なのだろうか?
[〜#〜] edit [〜#〜]もちろん、揮発性の代わりにfinal AtomicReference
または類似。
[〜#〜] edit [〜#〜]、たとえば、 getterメソッドはJavaのvolatileに代わるものですか?
[〜#〜] edit [〜#〜]混乱を避けるために:この質問はキャッシュの無効化に関するものです!2つのスレッドが同じオブジェクトで動作する場合、オブジェクトのフィールドは、揮発性として宣言されていなければ(スレッドごとに)キャッシュできます。キャッシュが適切に無効化されることをどのように保証できますか?
最終編集JLS§17(Javaメモリモデル)を指摘してくれた@Peter Lawreyに感謝します。私が見る限り、同期は操作間の発生前の関係を確立するため、それらの更新が「前に発生した」場合、スレッドは別のスレッドからの更新を見ることができます。不揮発性フィールドのゲッターとセッターがsynchronized
の場合。
私は_private final
_がおそらくvar
のようなキーワードを持つフィールドと変数のデフォルトである必要があると感じていますが、必要でない場合はvolatileを使用します
final
とは異なり、これが変更されるべきではないことを明確にするために、不要な場合にvolatile
を使用すると、なぜ揮発性になったのかを読者が理解しようとするため混乱する可能性があります。フィールドを変更する必要がある場合(別のオブジェクトをポイントし、プリミティブ値を更新する)、フィールドは揮発性である必要があるため、他のすべてのスレッドは新しい値で動作します。
これは読み取りには適していますが、この些細なケースを考慮してください。
_volatile int x;
x++;
_
これはスレッドセーフではありません。と同じです
_int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;
_
さらに悪いことに、volatile
を使用すると、この種のバグを見つけるのが難しくなります。
Yshavitが指摘しているように、複数のフィールドを更新することはvolatile
で回避するのが困難です。 HashMap.put(a, b)
は複数の参照を更新します。
キャッシュされた値を返す可能性があるため、このフィールドにアクセスするメソッドの同期だけでは不十分です。
synchronizedはvolatile
以上のすべてのメモリ保証を提供します。これが大幅に遅い理由です。
注:すべてのメソッドをsynchronized
- ingするだけでは、必ずしも十分ではありません。 StringBuffer
はすべてのメソッドを同期させますが、マルチスレッドコンテキストでは、使用がエラーになりやすいため、役に立たないよりも最悪です。
スレッドの安全性を達成することは、妖精の粉をまき散らすようなものであると仮定するのはあまりにも簡単です。問題は、スレッドの安全性が多くの穴のあるバケツに似ていることです。最大の穴を塞ぐと、バグはなくなるように見えますが、すべてを塞がない限り、スレッドの安全性はありませんが、見つけるのは難しくなります。
同期と揮発性の観点から、これは
揮発性変数の読み取りや書き込み、Java.util.concurrentパッケージのクラスの使用などの他のメカニズムは、同期の代替方法を提供します。
https://docs.Oracle.com/javase/specs/jls/se7/html/jls-17.html
スレッド化の問題に関係なく、final
を変更する必要のないフィールドを作成することをお勧めします。クラスのインスタンスの状態をより簡単に知ることができるため、クラスのインスタンスの推論が容易になります。
他のフィールドの作成volatile
:
キャッシュされた値を返す可能性があるため、このフィールドにアクセスするメソッドの同期だけでは不十分です。
同期ブロック外の値にアクセスした場合にのみ、キャッシュされた値が表示されます。
すべてのアクセスを正しく同期する必要があります。 1つの同期ブロックの終了は、別の同期ブロックの開始前に発生することが保証されています(同じモニターで同期する場合)。
同期を使用する必要がある場合が少なくともいくつかあります。
Atomic*
クラスを使用できる場合。ただし、1つのフィールドを更新する場合でも、排他的アクセスが必要になる場合があります(たとえば、ある要素をリストに追加し、別の要素を削除するなど)。ArrayList
や配列などのスレッドセーフでない値には不十分な場合があります。オブジェクトがスレッド間で共有される場合、2つの明確なオプションがあります。
1。オブジェクトを読み取り専用にする
そのため、更新(またはキャッシュ)は影響しません。
2。オブジェクト自体で同期する
キャッシュの無効化は困難です。 非常に難しい。 したがって、古い値がないことを保証する必要がある場合は、その値を保護し、その値の周りのロックを保護する必要があります。
共有オブジェクトでロックと値をプライベートにするため、ここでの操作は実装の詳細です。
デッドロックを回避するには、他のロックとの相互作用を避けるために、この操作は「アトミック」でなければなりません。