web-dev-qa-db-ja.com

Java-の揮発性フィールドと同期ブロックとの以前の関係と、それらの不揮発性変数への影響?

私はまだスレッドの概念にかなり慣れていないので、スレッドについてもっと理解しようとしています。最近、ジェレミー・マンソンによる Javaでの揮発性の意味 に関するブログ投稿を見つけました。

1つのスレッドが揮発性変数に書き込み、別のスレッドがその書き込みを確認すると、最初のスレッドは2番目のスレッドにメモリの内容のallを伝えますその揮発性変数への書き込みを実行しました。 [...]allは、スレッド1が[volatile] readyに書き込む前に、メモリ2に表示される内容を、スレッド2に表示する必要があります。 trueの値readyを読み取った後。 [自分で追加した強調]

さて、それは、揮発性変数への書き込み時にスレッド1のメモリに保持されているすべての変数(揮発性かどうか)が、その揮発性変数を読み取った後、スレッド2から見えるようになるということですか?もしそうなら、公式のJava documentation/Oracleソースからそのステートメントを一緒に謎にすることは可能ですか?そして、どのバージョンのJava以降は動作しますか?

特に、すべてのスレッドが次のクラス変数を共有する場合:

private String s = "running";
private volatile boolean b = false;

そして、スレッド1は最初に以下を実行します。

s = "done";
b = true;

その後、スレッド2が実行されます(スレッド1がvolatileフィールドに書き込んだ後)。

boolean flag = b; //read from volatile
System.out.println(s);

これは「完了」を印刷することが保証されますか?

bvolatileとして宣言する代わりに、書き込みと読み取りをsynchronizedに入れたらどうなりますかブロック?

また、「 静的変数はスレッド間で共有されますか? 」というタイトルのディスカッションでは、@ TREE writes

Volatileを使用して複数の共有状態を保護しないでください。

理由(申し訳ありません。他の質問にはまだコメントできません。そこ...)

49
Christian

はい、スレッド2が "done"を出力することが保証されています。もちろん、スレッド1のbへの書き込みが、スレッド2のbからの読み取りの前に、同時またはそれ以前に発生するのではなく、実際に発生する場合です。

ここでの推論の中心は happens-before relationship です。マルチスレッドプログラムの実行は、イベントで構成されていると見なされます。イベントは、発生前の関係によって関連付けることができます。つまり、あるイベントが別のイベントの前に発生します。 2つのイベントが直接関連していなくても、あるイベントから別のイベントへの前に発生する関係のチェーンを追跡できれば、一方が他方の前に発生すると言えます。

あなたの場合、次のイベントがあります:

  • スレッド1はsに書き込みます
  • スレッド1はbに書き込みます
  • スレッド2はbから読み取ります
  • スレッド2はsから読み取ります

そして、以下のルールが作用します:

  • 「xとyが同じスレッドのアクションであり、xがプログラム順でyの前にある場合、hb(x、y)。」 (プログラム順序ルール)
  • 「揮発性フィールドへの書き込み(8.3.1.4)は、そのフィールドの後続のすべての読み取りの前に発生します。」 (volatileルール)

したがって、次の発生前の関係が存在します。

  • スレッド1がsに書き込む前に発生するスレッド1がbに書き込む(プログラム順序ルール) )
  • スレッド1がbに書き込む前に発生するスレッド2がbから読み取る(揮発性ルール)
  • スレッド2がbから読み取る前に発生するスレッド2がsから読み取る(プログラム順序ルール) )

そのチェーンをたどると、結果として次のことがわかります。

  • スレッド1がsに書き込む前にスレッド2がsから読み取る
34
Tom Anderson

Bをvolatileとして宣言する代わりに、書き込みと読み取りを同期ブロックに配置するとどうなりますか?

If and only ifこのような同期されたすべてのブロックを同じロックで保護すると、volatileの例と同じように可視性が保証されます。さらに、そのような同期されたブロックの実行を相互に排除します。

Volatileを使用して複数の共有状態を保護しないでください。

どうして?

volatileは原子性を保証しません。この例では、表示している書き込みの後に、s変数が他のスレッドによって変更された可能性があります。読み取りスレッドには、どの値が表示されるかについての保証はありません。 sの読み取り後、ただしvolatileの読み取り前に発生するsへの書き込みについても同様です。

安全で実際に行われることは、不変状態の共有volatile変数に書き込まれた参照から推移的にアクセスできることです。つまり、それが「共有状態の1つの部分」が意図する意味かもしれません。

公式のJavaドキュメント/ Oracleソースから)そのステートメントを一緒に困惑させることは可能ですか?

仕様からの引用:

17.4.4。同期の順序

揮発性変数v(8.3.1.4)への書き込みは、任意のスレッドによるvの後続のすべての読み取りと同期します(「後続」は同期順序に従って定義されます)。

17.4.5。注文前に発生

Xとyが同じスレッドのアクションであり、xがプログラム順にyの前にある場合、hb(x、y)です。

アクションxが次のアクションyと同期する場合、hb(x、y)も得られます。

これで十分でしょう。

そして、Java以降のどのバージョンから、これは動作しますか?

Java言語仕様、第3版では、上記の保証の鍵となるメモリモデル仕様の書き換えが導入されました。 NB以前のほとんどのバージョンは、保証が存在するかのように動作し、多くのコード行が実際にそれに依存していました。保証が実際に存在しないことを知ったとき、人々は驚きました。

17
Marko Topolnik

これは「完了」を印刷することが保証されますか?

Java Concurrency in Practice で述べたように:

スレッドAvolatile変数に書き込み、その後スレッドBが同じ変数を読み取る場合、に表示されていたすべての変数の値volatile変数への書き込み前のAは、volatile変数を読み取った後、Bに表示されます。

したがって、[〜#〜]はい[〜#〜]、これにより、「完了」の印刷が保証されます。

Bをvolatileとして宣言する代わりに、書き込みと読み取りを同期ブロックに配置するとどうなりますか?

これも同じことを保証します。

Volatileを使用して複数の共有状態を保護しないでください。

どうして?

なぜなら、volatileは可視性のみを保証します。原子性は保証されません。スレッドAによってアクセスされているメソッドに2つの揮発性書き込みがあり、別のスレッドBがそれらの揮発性変数にアクセスしている場合、スレッドAがメソッドを実行している間スレッドAは、操作の途中でスレッドBによってプリエンプトされる可能性があります(たとえば、最初の揮発性書き込みの後、スレッドAによる2番目の揮発性書き込みの前)。したがって、操作の原子性を保証することは、synchronizationが最も実現可能な方法です。

15
Vishal K