DCL が失敗する理由とその修正方法については、私がずっと前に StackOverflow で提供したこの回答を確認してください。
問題は同期/非同期ではありません。問題は、並べ替えと呼ばれるものです。
JVM 仕様では、事前発生関係と呼ばれるものを定義しています。単一のスレッド内で、ステートメント S1 がステートメント S2 の前にある場合、S1 は S2 の前に発生します。つまり、 S1 がメモリに対して行った変更はすべて S2 に表示されます。ステートメント S1 を S2 の前に実行する必要があるとは言っていないことに注意してください。S1がS2の前に実行されたかのように見えるべきだと言っているだけです。たとえば、次のコードを検討してください。
int x = 0;
int y = 0;
int z = 0;
x++;
y++;
z++;
z += x + y;
System.out.println(z);
ここでは、JVM が 3 つのインクリメント ステートメントを実行する順序は重要ではありません。唯一の保証は、実行時に x、y、および z の値がすべて 1 でなければならないということです。実際、並べ替えが先行発生関係にz += x + y
違反しない場合、JVM は実際にステートメントを並べ替えることができます。これは、コードを少し並べ替えるだけでコードが最適化され、パフォーマンスが向上する場合があるためです。
欠点は、JVM が複数のスレッドを使用するときに非常に奇妙な結果につながる可能性のある方法で物事を並べ替えることができることです。例えば:
class Broken {
private int value;
private boolean initialized = false;
public void init() {
value = 5;
initialized = true;
}
public boolean isInitialized() { return initialized; }
public int getValue() { return value; }
}
スレッドが次のコードを実行しているとします。
while (!broken.isInitialized()) {
Thread.sleep(1); // patiently wait...
}
System.out.println(broken.getValue());
ここで、別のスレッドが同じBroken
インスタンスで、
broken.init();
JVM は、最初に を実行し、次に を 5 に設定することinit()
により、メソッド内のコードを並べ替えることができます。これが発生すると、初期化を待機している最初のスレッドが 0! を出力する可能性があります。修正するには、両方のメソッドに追加するか、フィールドに追加します。initialized = true
value
synchronized
volatile
initialized
DCL に戻ると、シングルトンの初期化が別の順序で実行される可能性があります。例えば:
1. mem = allocateMem() : allocate memory for Test and save it's address.
2. construct(mem) : construct the class Test
3. t = mem : point t to the mem
次のようになります。
1. mem = allocateMem() : allocate memory for Test and save it's address.
2. t = mem : point t to the mem
3. construct(mem) : construct the class Test
単一のスレッドの場合、両方のブロックが完全に同等であるためです。とはいえ、この種のシングルトン初期化は、シングル スレッド アプリケーションに対して完全に安全であることは間違いありません。ただし、複数のスレッドの場合、1 つのスレッドが部分的に初期化されたオブジェクトの参照を取得する可能性があります。
複数のスレッドを使用するときにステートメント間の事前発生関係を確保するには、ロックの取得/解放と揮発性フィールドの読み取り/書き込みの 2 つの可能性があります。DCL を修正するには、 singleton を保持するフィールドを宣言する必要がありますvolatile
。これにより、シングルトンを保持するフィールドの読み取りの前に、シングルトンの初期化 (つまり、そのコンストラクターの実行) が確実に行われます。volatile が DCL をどのように修正するかについてのやや詳細な説明については、この回答の上部にリンクされている回答を確認してください。