Javaのプログラミングを行っているのですが、volatileをつけた結果になっとくがいきません。
volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる
とあるのですが、出力結果からしてそうじゃないんじゃないかと考えています。
下記のソースの実行結果がなぜそうなるのかいまいち分かっていません。
public class Thirsty {
//ボトル
static Bottle bottle;
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
bottle = new Bottle(2000);
// ボトルから水を飲む人
class Person extends Thread {
public Person(String name) {
super(name);
}
public void run() {
// 200ml飲んでみる。飲めたらtrueが返される
while(true) {
if(bottle.drink(10)) {
System.out.println(Thread.currentThread().getName() + " did drink water ");
} else {
System.out.println(Thread.currentThread().getName() + " couldn't drink water ");
break;
}
}
}
}
Person yamada = new Person("Yamada");
Person tanaka = new Person("Tanaka");
yamada.start();
tanaka.start();
}
}
class Bottle {
private volatile int water;// volatileを使用した場合
Bottle(int amount) {
water = amount;
}
// ボトルにamountで指定した量だけ残っているか
private boolean contains(int amount) {
if(amount <= water) {
return true;
} else {
return false;
}
}
// ボトルの水をamountで指定した量だけ水を飲む
public boolean drink(int amount) {
if(contains(amount)) {//水があれば残りの量から引く 残量を出力する
water -= amount;
System.out.println(Thread.currentThread().getName() + " water = "+water);
return true;
}
return false;
}
}
私の場合、出力結果で、最後の方には
Yamada did drink water
Yamada water = 0
Yamada did drink water
Yamada couldn't drink water
Tanaka water = 80
Tanaka did drink water
Tanaka couldn't drink water
となります。 途中でも waterの値がいきなり高くなったりします。
なぜこのような結果がでるのでしょうか?
volatileというのはメインメモリの値を必ず参照するとあるのですが、このメインメモリというのは
class Thirstyのメモリを指すのでしょうか?
No.2ベストアンサー
- 回答日時:
実はクラスのフィールドには複数のコピーが存在します。
その存在場所がメインメモリと作業用メモリです。
メインメモリにあるものがマスターコピーであり、
スレッドごとに存在する作業用メモリ上にそのコピーが置かれます。
フィールドの読み書きはこの作業用メモリにあるコピーに対して行われ、
適宜マスターコピーとの間で同期が取られます。
これは実行効率を上げるために行われています。
volatileなフィールドの場合はマスターコピーがフィールドの読み書きでの操作対象になります。
> volatileというのはメインメモリの値を必ず参照する
というのがまさにこれで、マスターへのアクセスが行われることが保証されます。
> volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる
わけではなく、
どのスレッドも確実にそのフィールド(のマスターコピー)をその時点で書き換えることを保証するということです。
したがって、volatileは同期制御が不要になる魔法の呪文ではありません。
どのスレッドも自分の好き勝手なタイミングでフィールドを書き換えるのはvolatileを付けても同じです。
ただ、作業用コピーに対して行い同期はシステムに任せるのか、直接マスターコピーに対して行うのかの違いだけです。
質問者のサンプルでいえばcontainsとdrinkにsynchronizedを付ける等、
同期を取る仕組みを加えないとおかしなことになります。
volatileは最適化によってマスターコピーが変更されない事態を防ぐことの他に、
double型やlong型のようなメイン-作業間でのアトミックな扱いが保証されていないような型のフィールドを
マルチスレッドで扱えるようにするためにもあります。
64ビット幅のdouble型やlong型はマスターコピーと作業用コピーとの間でやりとりする時に、
一度に同期が取られることが保証されていません(非アトミックな扱い)。
32ビット幅分ずつで2回でコピーされうるので、
例えばあるスレッドから32ビット分だけ作業用コピーからマスターへコピーされた時点で、
他のスレッドによってマスターコピーが読まれるような事態が起こりえます。
volatileはこれを防ぐことができ、
全幅64ビット分の同期が終了しない限り他のスレッドからアクセスされなくなります。
この機能が
> volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる
という誤解を生んでいるんじゃないかなとも思ったりします。
volatileについてはJava言語仕様の他、Java仮想マシン仕様に詳細があるので、
これらの文書の中をvolatileをキーワードに探してみてください。
回答ありがとうございます。
大変参考になりました。
サイトを見て周ってもあまりよく分からなかったのでここで質問をしました。
volatileとメインメモリ、作業用メモリとの関係が理解できました。
volatileではスレッドの割り込みを防ぐことはできなんですね。
No.4
- 回答日時:
他の方も書いているとおり、
>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の扱いはまだこれからなので大変参考になります。
No.3
- 回答日時:
少し訂正。
> この機能が
>> volatileというのは、他のスレッドによって変数の値が書き換えられる状態になるのを防ぐことができる
> という誤解を生んでいるんじゃないかなとも思ったりします。
作業用コピー間の同期という点では割り込まれないことが保証されているので誤解ではないですね。
正確には、だから同期処理を行わなくてもいいというのが誤解ですね。
質問者のサンプルでいえば、drinkの中で、
water -= amount;
の箇所がwaterがvolatileであってもアトミックには行われない(waterの値を読み出すことと引いた値を代入することは分割されうる)ことや、
containsの実行とwaterを減らす処理の間に状況が変わりうることに対する手当ては必要ということです。
No.1
- 回答日時:
≫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があると割り込まれやすいようです。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
- ・漫画をレンタルでお得に読める!
- ・人生のプチ美学を教えてください!!
- ・10秒目をつむったら…
- ・あなたの習慣について教えてください!!
- ・牛、豚、鶏、どれか一つ食べられなくなるとしたら?
- ・【大喜利】【投稿~9/18】 おとぎ話『桃太郎』の知られざるエピソード
- ・街中で見かけて「グッときた人」の思い出
- ・「一気に最後まで読んだ」本、教えて下さい!
- ・幼稚園時代「何組」でしたか?
- ・激凹みから立ち直る方法
- ・1つだけ過去を変えられるとしたら?
- ・【あるあるbot連動企画】あるあるbotに投稿したけど採用されなかったあるある募集
- ・【あるあるbot連動企画】フォロワー20万人のアカウントであなたのあるあるを披露してみませんか?
- ・映画のエンドロール観る派?観ない派?
- ・海外旅行から帰ってきたら、まず何を食べる?
- ・誕生日にもらった意外なもの
- ・天使と悪魔選手権
- ・ちょっと先の未来クイズ第2問
- ・【大喜利】【投稿~9/7】 ロボットの住む世界で流行ってる罰ゲームとは?
- ・推しミネラルウォーターはありますか?
- ・都道府県穴埋めゲーム
- ・この人頭いいなと思ったエピソード
- ・準・究極の選択
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
ResultSetの内部構造(Java)
-
DateTimePickerに値を入れたい...
-
結合した文字列をファイル名に...
-
ダブルクォーテーションを含む...
-
ArrayListの要素数の上限
-
matchesを否定文として使う方法...
-
DOSバッチで変数の値を変数名に...
-
日付や時刻の"01"を" 1"に変換...
-
PSQLExceptionが発生する
-
JSPでの計算結果表示
-
getParameterの値変更
-
初回のスピンボタンの挙動
-
C言語の変数(LSB)の合わせ込...
-
BCD形式で時刻を!
-
VB6,論理演算子Orの使い方がわ...
-
JSONでデータを送る(変数) JAVA
-
javaのCSVデータ読込についてです
-
MySQLのblob型の日本語文字列を...
-
【Excel VBA】繰り返し処理がで...
-
char型での演算子
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
ダブルクォーテーションを含む...
-
wsprintf関数の使い方について
-
javaのCSVデータ読込についてです
-
ArrayListの要素数の上限
-
BCD形式で時刻を!
-
DateTimePickerに値を入れたい...
-
excel vba 時間計算と条件分岐...
-
日付や時刻の"01"を" 1"に変換...
-
Stringクラスの変数の格納アド...
-
C言語の変数(LSB)の合わせ込...
-
Evaluate()に文字列の形式の数...
-
char型での演算子
-
実行シェルからCOBOLへパラメー...
-
DOSバッチで変数の値を変数名に...
-
レジストリの値の取得のデータ...
-
hiddenの値を消したくない!
-
結合した文字列をファイル名に...
-
ResultSetの内部構造(Java)
-
String型の値が大文字か小文字...
-
javaの演算子の部分ですが 4行...
おすすめ情報