web-dev-qa-db-ja.com

Java:すべてのフィールドをfinalまたはvolatileにしますか?

スレッド間で共有されるオブジェクトがある場合、すべてのフィールドはfinalまたはvolatileのいずれかであるように思えますが、次の理由があります。

  • フィールドを変更する必要がある場合(別のオブジェクトをポイントし、プリミティブ値を更新する)、フィールドはvolatileにして、他のすべてのスレッドが新しい値で動作するようにします。キャッシュされた値を返す可能性があるため、このフィールドにアクセスするメソッドの同期だけでは不十分です。

  • フィールドを変更しない場合は、finalにします。

しかし、私はこれについて何も見つけることができなかったので、この論理に欠陥があるのか​​、それともあまりにも明白なのだろうか?

[〜#〜] edit [〜#〜]もちろん、揮発性の代わりにfinal AtomicReference または類似。

[〜#〜] edit [〜#〜]、たとえば、 getterメソッドはJavaのvolatileに代わるものですか?

[〜#〜] edit [〜#〜]混乱を避けるために:この質問はキャッシュの無効化に関するものです!2つのスレッドが同じオブジェクトで動作する場合、オブジェクトのフィールドは、揮発性として宣言されていなければ(スレッドごとに)キャッシュできます。キャッシュが適切に無効化されることをどのように保証できますか?

最終編集JLS§17(Javaメモリモデル)を指摘してくれた@Peter Lawreyに感謝します。私が見る限り、同期は操作間の発生前の関係を確立するため、それらの更新が「前に発生した」場合、スレッドは別のスレッドからの更新を見ることができます。不揮発性フィールドのゲッターとセッターがsynchronizedの場合。

23
Moritz

私は_private final_がおそらくvarのようなキーワードを持つフィールドと変数のデフォルトである必要があると感じていますが、必要でない場合はvolatileを使用します

  • はるかに遅く、多くの場合10倍ほど遅くなります。
  • 通常、必要なスレッドの安全性は得られませんが、そのようなバグを見つけにくくすることで、そのようなバグを見つけにくくすることができます。
  • 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

31
Peter Lawrey

スレッド化の問題に関係なく、finalを変更する必要のないフィールドを作成することをお勧めします。クラスのインスタンスの状態をより簡単に知ることができるため、クラスのインスタンスの推論が容易になります。

他のフィールドの作成volatile

キャッシュされた値を返す可能性があるため、このフィールドにアクセスするメソッドの同期だけでは不十分です。

同期ブロック外の値にアクセスした場合にのみ、キャッシュされた値が表示されます。

すべてのアクセスを正しく同期する必要があります。 1つの同期ブロックの終了は、別の同期ブロックの開始前に発生することが保証されています(同じモニターで同期する場合)。

同期を使用する必要がある場合が少なくともいくつかあります。

  • 1つ以上のフィールドをアトミ​​ックに読み取り、更新する必要がある場合は、同期を使用します。
    • 特定の単一フィールドの更新の同期を回避できる場合があります。 「プレーン古いフィールド」の代わりにAtomic*クラスを使用できる場合。ただし、1つのフィールドを更新する場合でも、排他的アクセスが必要になる場合があります(たとえば、ある要素をリストに追加し、別の要素を削除するなど)。
  • また、volatile/finalは、ArrayListや配列などのスレッドセーフでない値には不十分な場合があります。
8
Andy Turner

オブジェクトがスレッド間で共有される場合、2つの明確なオプションがあります。

1。オブジェクトを読み取り専用にする

そのため、更新(またはキャッシュ)は影響しません。

2。オブジェクト自体で同期する

キャッシュの無効化は困難です。 非常に難しい。 したがって、古い値がないことを保証する必要がある場合は、その値を保護し、その値の周りのロックを保護する必要があります。

共有オブジェクトでロックと値をプライベートにするため、ここでの操作は実装の詳細です。

デッドロックを回避するには、他のロックとの相互作用を避けるために、この操作は「アトミック」でなければなりません。

1