例 :
class Something{
private volatile static Something instance = null;
private int x;
private int y;
private Something(){
this.x = 1;
this.y = 2;
}
public static Something getInstance() {
if (instance == null) {
synchronized (Something.class) {
if (instance == null)
instance = new Something();
}
}
}
return instance;
}
}
説明 :
上記のコードがあるとしましょう:
ここで、インスタンスがしばらくの間揮発性ではないと仮定しましょう。
スレッド#1:
getInstanceメソッドを呼び出し、インスタンス値をチェックします{null以降}、IF条件内に入ります。ロックにアクセスすると、インスタンス== nullが再び検出され、Somethingコンストラクターが呼び出されます。コンストラクター本体の内部に入ります。
スレッド#1がコンストラクター本体の内部に入るとすぐに、コンテキストスイッチが発生し、スレッド#2が実行されるようになります。
スレッド#2:
呼び出しはインスタンスを取得しますが、突然そのインスタンスがnullではないことに気付きますか?なぜ{理由はこの直後に議論する}ので、部分的に構築されたオブジェクトを参照に割り当てて返します。
現在の状況は次のようになります。Thread#1はまだオブジェクトを完全に構築する必要があります{完全に構築する必要があります}そしてThread#2は部分的に構築されたオブジェクトの参照を取得し、それをsayreference.xのように使用すると//"xを出力します「1」ではなく「デフォルト値」
スレッド#2の場合、部分的に構築されたオブジェクト参照が返されるのはなぜですか?理由は単純です:ステートメントの並べ替え。手順は、オブジェクトの作成と参照の関連付けが簡単です。
- ヒープにメモリを割り当てます。
- クラスメンバーを初期化するコンストラクターの本体を実行します。
- 上記の手順が完了したら、新しく作成されたオブジェクトへの参照。
ただし、コンパイラがこれらの命令を順不同で実行する場合があります。つまり、次の
ようになります。
- ヒープにメモリを割り当てます。
- 新しく作成されたオブジェクトへの参照。
- クラスメンバーを初期化するコンストラクターの本体を実行します。
上記の2つの手順が実行され、コンテキスト切り替えが発生した場合、参照は初期化されていないオブジェクトを指すか、Inside Constructor Bodyコンテキスト切り替えが発生した場合、参照は部分的に初期化されたオブジェクトを参照します。
このようなシナリオが発生した場合、参照はnullでも完全でもないため、シングルトンの動機が失われます。
さて、どのように揮発性がそのような困惑から私たちの命を救うか:
私たちが知っているように、揮発性は2つの原則で働きます:1)可視性2)関係の前に起こります。関係がここに現れる前に今起こります。
したがって、参照は揮発性の書き込みであるため、すべてのステートメントは揮発性の書き込みの前に実行する必要があります。オブジェクトの構築手順を見ると、次のようになります。
- オブジェクトにメモリを割り当てます
- メンバー変数を初期化します{コンストラクター本体}
- オブジェクト参照を揮発性変数インスタンスに割り当てます。
ステップ3には揮発性変数の書き込みがあり、以前のように..すべてのステートメントの書き込みはステップ3で使用できることが保証されています。また、揮発性であるため、揮発性ステートメントと不揮発性ステートメントの間で並べ替えは発生しません。これは、古いJavaでは当てはまりませんでした。メモリモデル。
したがって、ステップ3を実行する前に、ステップ1とステップ2が実行され、ステップ3で使用できることが保証されます。{ステップ1とステップ2が実行される順序については、気にしません。}
したがって、このスレッドから、完全に作成されたオブジェクトまたはnullが表示されます