Double-Checkにおけるvolatile作用
3047 ワード
「張新強」のブログから転載
完璧に見える単例モード?の最初のif(instance==null)は、効率の問題を解決するために、instanceがnullの場合にのみsynchronizedのコードセグメントに入る--確率が大幅に減少した. の2番目のif(instance==null)は、複数のインスタンスが発生する可能性を防止するためである.
では、また問題がありますか?
答え:『見た目』だけで、まだ小さな確率で問題が発生します.
原子操作(atomic)は分割不可能な操作であり,コンピュータではスレッドスケジューリングによって中断されない操作を指す.
mの元の値が0である場合、この操作に対して、実行に成功したmが6になったか、mが実行されていないか0になったかのいずれかであり、m=3のような中間状態は、同時スレッドにおいても現れない.
一方、宣言して値を割り当てるのは原子操作ではありません.
この文について、少なくとも2つの操作があります.1つの変数n 2がnに6を割り当てます.これにより、変数nが宣言されていますが、まだ割り当てられていない状態が中間状態になります.--このように、マルチスレッドでは、スレッド実行順序の不確実性のため、両方のスレッドがmを使用すると、不安定な結果が生じる可能性がある.
コンセプト
簡単に言えば、コンピュータは実行効率を高めるためにいくつかの最適化を行い、最終結果に影響を与えない場合、いくつかの文の実行順序を調整する可能性があります.
通常、シーケンス構造の場合、実行されるシーケンスは、上から下、すなわち1234である.ただし、命令の並び替えのため、最終的な結果に影響を及ぼさないため、実際に実行される順序が3124または1324になる可能性がある.文3と4には原子性の問題がないため、文3と文4も原子操作に分割され、再配置される可能性があります.--つまり、
非原子的な操作では、最終結果に影響を及ぼさない場合、分割された原子操作が実行順序を再配置される可能性がある.
主にsingleton=new Singleton()という文にありますが、これは原子操作ではありません.実際にJVMでは次の3つのことをしています. singletonにメモリを割り当てる Singletonのコンストラクタを呼び出してメンバー変数を初期化し、インスタンス を形成する. singletonオブジェクトを割り当てられたメモリ領域(singletonがnullではない) に指し示します.
ただし、JVMのインスタント・コンパイラでは、コマンドの再ソートの最適化が存在します.すなわち、上記の2ステップ目と3ステップ目の順序は保証されず、最終的な実行順序は1−2−3であっても1−3−2であってもよい.後者であれば、3実行完了、2未実行の前にスレッド2によってプリエンプトされ、このときinstanceは既にnullではない(ただし初期化されていない)ので、スレッド2は直接instanceに戻り、使用し、そのままエラーを報告します.
もう少し説明すると、『instanceはnullではないがまだ初期化が完了していない』という中間状態があるので、このとき、他のスレッドがちょうど第1層if(instance==null)まで動作している場合、ここで読み取ったinstanceはnullではないので、この中間状態のinstanceをそのまま持って行って使用すると、問題が発生します.ここで重要なのは、スレッドT 1のinstanceに対する書き込みが完了していないことであり、スレッドT 2は読み取り操作を実行することである.
volatileキーワードの1つの役割は、命令の再配置を禁止し、instanceをvolatileと宣言すると、その書き込み操作にメモリバリア(メモリバリアとは何ですか?)があります.これにより、その付与が完了する前に、読み取り操作を呼び出す必要はありません.
注意:volatileがブロックするのはsingleton=new Singleton()という文の内部[1-2-3]の命令の再配置ではなく、1つの書き込み操作([1-2-3])が完了するまで読み取り操作を呼び出さないことを保証します(if(instance==null)).
完璧に見える単例モード?
public class Single {
private static Single3 instance;
private Single() {}
public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single3();
}
}
}
return instance;
}
}
では、また問題がありますか?
答え:『見た目』だけで、まだ小さな確率で問題が発生します.
げんしさぎょ
原子操作(atomic)は分割不可能な操作であり,コンピュータではスレッドスケジューリングによって中断されない操作を指す.
:
m = 6; //
mの元の値が0である場合、この操作に対して、実行に成功したmが6になったか、mが実行されていないか0になったかのいずれかであり、m=3のような中間状態は、同時スレッドにおいても現れない.
一方、宣言して値を割り当てるのは原子操作ではありません.
int n = 6; //
この文について、少なくとも2つの操作があります.1つの変数n 2がnに6を割り当てます.これにより、変数nが宣言されていますが、まだ割り当てられていない状態が中間状態になります.--このように、マルチスレッドでは、スレッド実行順序の不確実性のため、両方のスレッドがmを使用すると、不安定な結果が生じる可能性がある.
めいれいさいはいち
コンセプト
簡単に言えば、コンピュータは実行効率を高めるためにいくつかの最適化を行い、最終結果に影響を与えない場合、いくつかの文の実行順序を調整する可能性があります.
:
int a ; // 1
a = 8 ; // 2
int b = 9 ; // 3
int c = a + b ; // 4
通常、シーケンス構造の場合、実行されるシーケンスは、上から下、すなわち1234である.ただし、命令の並び替えのため、最終的な結果に影響を及ぼさないため、実際に実行される順序が3124または1324になる可能性がある.文3と4には原子性の問題がないため、文3と文4も原子操作に分割され、再配置される可能性があります.--つまり、
非原子的な操作では、最終結果に影響を及ぼさない場合、分割された原子操作が実行順序を再配置される可能性がある.
話題にもどる
主にsingleton=new Singleton()という文にありますが、これは原子操作ではありません.実際にJVMでは次の3つのことをしています.
ただし、JVMのインスタント・コンパイラでは、コマンドの再ソートの最適化が存在します.すなわち、上記の2ステップ目と3ステップ目の順序は保証されず、最終的な実行順序は1−2−3であっても1−3−2であってもよい.後者であれば、3実行完了、2未実行の前にスレッド2によってプリエンプトされ、このときinstanceは既にnullではない(ただし初期化されていない)ので、スレッド2は直接instanceに戻り、使用し、そのままエラーを報告します.
:
もう少し説明すると、『instanceはnullではないがまだ初期化が完了していない』という中間状態があるので、このとき、他のスレッドがちょうど第1層if(instance==null)まで動作している場合、ここで読み取ったinstanceはnullではないので、この中間状態のinstanceをそのまま持って行って使用すると、問題が発生します.ここで重要なのは、スレッドT 1のinstanceに対する書き込みが完了していないことであり、スレッドT 2は読み取り操作を実行することである.
完全版
public class Single {
private static volatile Single4 instance;
private Single() {}
public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
}
return instance;
}
}
volatileが果たす役割
volatileキーワードの1つの役割は、命令の再配置を禁止し、instanceをvolatileと宣言すると、その書き込み操作にメモリバリア(メモリバリアとは何ですか?)があります.これにより、その付与が完了する前に、読み取り操作を呼び出す必要はありません.
注意:volatileがブロックするのはsingleton=new Singleton()という文の内部[1-2-3]の命令の再配置ではなく、1つの書き込み操作([1-2-3])が完了するまで読み取り操作を呼び出さないことを保証します(if(instance==null)).