- ベストアンサー
volatileの振る舞いが分かりません。
- volatileをつけた結果になっとくがいきません。
- volatileは他のスレッドによって変数の値が書き換えられる状態を防止する。
- ソースコードの出力結果が予想と異なる理由として、volatileがメインメモリを指すかどうかについて疑問がある。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
実はクラスのフィールドには複数のコピーが存在します。 その存在場所がメインメモリと作業用メモリです。 メインメモリにあるものがマスターコピーであり、 スレッドごとに存在する作業用メモリ上にそのコピーが置かれます。 フィールドの読み書きはこの作業用メモリにあるコピーに対して行われ、 適宜マスターコピーとの間で同期が取られます。 これは実行効率を上げるために行われています。 volatileなフィールドの場合はマスターコピーがフィールドの読み書きでの操作対象になります。 > volatileというのはメインメモリの値を必ず参照する というのがまさにこれで、マスターへのアクセスが行われることが保証されます。 > volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる わけではなく、 どのスレッドも確実にそのフィールド(のマスターコピー)をその時点で書き換えることを保証するということです。 したがって、volatileは同期制御が不要になる魔法の呪文ではありません。 どのスレッドも自分の好き勝手なタイミングでフィールドを書き換えるのはvolatileを付けても同じです。 ただ、作業用コピーに対して行い同期はシステムに任せるのか、直接マスターコピーに対して行うのかの違いだけです。 質問者のサンプルでいえばcontainsとdrinkにsynchronizedを付ける等、 同期を取る仕組みを加えないとおかしなことになります。 volatileは最適化によってマスターコピーが変更されない事態を防ぐことの他に、 double型やlong型のようなメイン-作業間でのアトミックな扱いが保証されていないような型のフィールドを マルチスレッドで扱えるようにするためにもあります。 64ビット幅のdouble型やlong型はマスターコピーと作業用コピーとの間でやりとりする時に、 一度に同期が取られることが保証されていません(非アトミックな扱い)。 32ビット幅分ずつで2回でコピーされうるので、 例えばあるスレッドから32ビット分だけ作業用コピーからマスターへコピーされた時点で、 他のスレッドによってマスターコピーが読まれるような事態が起こりえます。 volatileはこれを防ぐことができ、 全幅64ビット分の同期が終了しない限り他のスレッドからアクセスされなくなります。 この機能が > volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる という誤解を生んでいるんじゃないかなとも思ったりします。 volatileについてはJava言語仕様の他、Java仮想マシン仕様に詳細があるので、 これらの文書の中をvolatileをキーワードに探してみてください。
その他の回答 (3)
- notnot
- ベストアンサー率47% (4900/10358)
他の方も書いているとおり、 >volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる というのが全くのでたらめです。 int sum=0; int x=0; int i; for(i=0;i<10000;i++){ xやsumに関係ない他の処理; sum += x; } は、おそらく、 int sum=0; int x=1; int i; for(i=0;i<10000;i++){ xやsumに関係ない他の処理; } sum=10000; に最適化されます。ところが、x が他のスレッドで書き換わるなら、毎回xの値を調べて足し込む必要があります。その場合はvolatileをつけます。 「xは他のスレッドで書き換わる可能性があるよ」ということをコンパイラに知らせるのか゛volatileキーワードの役割です。 >volatileというのはメインメモリの値を必ず参照するとあるのですが、 変数の値を毎回参照すると言うことです。
お礼
詳しい説明ありがとうございます。 volatileの理解がまた一段と深まりました。 volatileの扱いはまだこれからなので大変参考になります。
- vaguechat
- ベストアンサー率85% (47/55)
少し訂正。 > この機能が >> volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる > という誤解を生んでいるんじゃないかなとも思ったりします。 作業用コピー間の同期という点では割り込まれないことが保証されているので誤解ではないですね。 正確には、だから同期処理を行わなくてもいいというのが誤解ですね。 質問者のサンプルでいえば、drinkの中で、 water -= amount; の箇所がwaterがvolatileであってもアトミックには行われない(waterの値を読み出すことと引いた値を代入することは分割されうる)ことや、 containsの実行とwaterを減らす処理の間に状況が変わりうることに対する手当ては必要ということです。
お礼
drinkの部分にはアトミックには行われない(作業用コピー間)ではない ということですね。
- luka3
- ベストアンサー率72% (424/583)
≫volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる 私の解釈では、どちらかというと逆の意味になると思います。 「他のスレッドによって変数の値が書き換えられる可能性があるのでコンパイラにより最適化されるのを防ぐことができる」 たとえば次のコードがあったとして int a, b, c; a = 1; b = 2; c = a + b; これをコンパイラは3行まとめて「c = 3」という最適化をしてしまいます(その方が実行速度が速くなりバイナリも小さくなるので)。 で、int volatile a; と宣言がされたなら、コンパイラは「c = a + 2」という最適化に抑えられ、cに代入する段階でaの値を必ずメモリ上から読み取る、ということになります。 (極端な例なので必ずこうなるかはわかりません。私の知識上の想像です) マルチスレッドや特殊な割り込み処理では、フラグなどがいつ更新されるかがとても重要であり、最適化されると困るためにvolatileという修飾子が必要になります。
お礼
回答ありがとうございます。
補足
コンパイラの最適化を防止するというのは他のサイトでも載ってありました。 しかし、このコードの出力結果とvolatileの関係が理解できなかったのです。 最後の出力結果の状態は、 Yamadaでwaterが0になるまえにTanakaに割り込まれ、waterの表示が0 なのにTanakaのwaterは80あるという事なんでしょうか? 再度試してみたところ、volatileが有る無し関係なく、割り込まれるようです。 ただvolatileがあると割り込まれやすいようです。
お礼
回答ありがとうございます。 大変参考になりました。 サイトを見て周ってもあまりよく分からなかったのでここで質問をしました。 volatileとメインメモリ、作業用メモリとの関係が理解できました。 volatileではスレッドの割り込みを防ぐことはできなんですね。