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で質問しましょう!
似たような質問が見つかりました
- 英語 英文の添削お願いします。【長文です。】 マッチングアプリで相手を言い負かしている時のやつです。 色々 1 2023/07/01 02:12
- Java java 引数 戻り値のあるメソッド 3 2023/02/12 06:23
- Java java 入力 3 4 3 出力 ABC DEFG HIJ このようなプログラムの書き方を教えてくだ 2 2022/07/15 14:18
- C言語・C++・C# C# DatagridviewにExcelシートを反映するとエラーが出る 2 2023/05/06 17:12
- Java JavaのSingletonパターンのprivateの持つ意味が分かりません。 5 2022/06/12 10:38
- C言語・C++・C# 大量のデータを読み込んで表示する速度を改善したい 8 2023/05/07 13:29
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- Ruby 【JAVA】数字をひし形に出力するプログラムについて 2 2022/07/11 23:32
- Java javaでのプログラム(配列)について質問です. 2 2022/10/14 22:27
- Java java final 1 2022/06/10 22:49
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
ダブルクォーテーションを含む...
-
ArrayListの要素数の上限
-
DateTimePickerに値を入れたい...
-
wsprintf関数の使い方について
-
JSPでHashMap・配列の変数の値...
-
Firefoxでvalueの値を変更できない
-
特定の文字列を複数抜き出した...
-
8桁整数を限りなく短い文字列に...
-
Stringクラスの変数の格納アド...
-
JSPでの計算結果表示
-
バイト配列からfloat型の数値を...
-
javaのCSVデータ読込についてです
-
javaの演算子の部分ですが 4行...
-
C#について質問です。文字列型(...
-
DOSバッチで変数の値を変数名に...
-
【Excel VBA】繰り返し処理がで...
-
Evaluate()に文字列の形式の数...
-
char型での演算子
-
実行シェルからCOBOLへパラメー...
-
VB.NET+Javascriptで、VB.NETで...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
ダブルクォーテーションを含む...
-
wsprintf関数の使い方について
-
javaのCSVデータ読込についてです
-
ArrayListの要素数の上限
-
特定の文字列を複数抜き出した...
-
Stringクラスの変数の格納アド...
-
結合した文字列をファイル名に...
-
BCD形式で時刻を!
-
DateTimePickerに値を入れたい...
-
Evaluate()に文字列の形式の数...
-
実行シェルからCOBOLへパラメー...
-
hiddenの値を消したくない!
-
DOSバッチで変数の値を変数名に...
-
C言語の変数(LSB)の合わせ込...
-
excel vba 時間計算と条件分岐...
-
Javaの問題なのですが、「3文字...
-
matchesを否定文として使う方法...
-
8桁整数を限りなく短い文字列に...
-
JSPでの計算結果表示
-
指定した日付が何日前なのかを...
おすすめ情報