private double value;
public synchronized void setValue(double value) {
this.value = value;
}
public double getValue() {
return this.value;
}
上記の例では、ゲッターを同期させることに何か意味がありますか?
private double value;
public synchronized void setValue(double value) {
this.value = value;
}
public double getValue() {
return this.value;
}
上記の例では、ゲッターを同期させることに何か意味がありますか?
ここでJava Concurrency in Practiceを引用するのが最善だと思います:
共有変数への書き込み時にのみ同期を使用する必要があると考えるのはよくある間違いです。これは単に真実ではありません。
複数のスレッドからアクセスされる可能性のある可変状態変数ごとに、その変数へのすべてのアクセスは、同じロックを保持して実行する必要があります。この場合、変数はそのロックによって保護されていると言います。
同期がない場合、コンパイラ、プロセッサ、およびランタイムは、操作が実行されているように見える順序に対して、まったく奇妙なことを行う可能性があります。同期が不十分なマルチスレッド プログラムでメモリ アクションが「発生する」順序について推論しようとすると、ほぼ間違いなく正しくありません。
通常、プリミティブについてはそれほど注意する必要はありint
ませboolean
ん。
スレッドが同期せずに変数を読み取ると、古い値が表示される場合がありますが、少なくとも、ランダムな値ではなく、スレッドによって実際に配置された値が表示されます。
ただし、これは 64 ビット操作、たとえば onlong
またはdouble
宣言されていない場合には当てはまりませんvolatile
。
Java メモリ モデルでは、フェッチ操作とストア操作がアトミックである必要がありますが、不揮発性の long 変数と double 変数の場合、JVM は 64 ビットの読み取りまたは書き込みを 2 つの別個の 32 ビット操作として扱うことができます。したがって、読み取りと書き込みが異なるスレッドで発生した場合、不揮発性 long を読み取り、ある値の上位 32 ビットと別の値の下位 32 ビットを取得することができます。
したがって、古い値を気にしなくても、共有された変更可能な long 変数と double 変数をマルチスレッド プログラムで使用するのは、それらが volatile と宣言されているか、ロックによって保護されていない限り、安全ではありません。
JIT がコードをコンパイルする合法的な方法を例を挙げて示しましょう。あなたが書く:
while (myBean.getValue() > 1.0) {
// perform some action
Thread.sleep(1);
}
JIT コンパイル:
if (myBean.getValue() > 1.0)
while (true) {
// perform some action
Thread.sleep(1);
}
わずかに異なるシナリオでは、Java コンパイラでさえ同様のバイトコードを生成できます (異なる への動的ディスパッチの可能性を排除するだけで済みますgetValue
)。これは巻き上げの教科書的な例です。
なぜこれが合法なのですか?myBean.getValue()
コンパイラには、上記のコードの実行中に の結果が決して変化しないと想定する権利があります。そうしないsynchronized
と、他のスレッドによるアクションを無視することができます。
誰かにとっては、このコードはひどく見えるかもしれませんが、非常にうまく機能します。
private Double value;
public void setValue(Double value){
updateValue(value, true);
}
public Double getValue(){
return updateValue(value, false);
}
private double updateValue(Double value,boolean set){
synchronized(MyClass.class){
if(set)
this.value = value;
return value;
}
}