二重チェックされたロックのシナリオでの順不同の書き込みについて言及されている例 (参照: IBM の記事とウィキペディアの記事)
コンストラクターが完全に初期化される前に Thread1 が同期ブロックから出てくる単純な理由を理解できませんでした。私の理解によると、「新規」の作成と呼び出しコンストラクターは順番に実行する必要があり、同期ロックはすべての作業が完了するまで解放されるべきではありません。
ここで何が欠けているか教えてください。
二重チェックされたロックのシナリオでの順不同の書き込みについて言及されている例 (参照: IBM の記事とウィキペディアの記事)
コンストラクターが完全に初期化される前に Thread1 が同期ブロックから出てくる単純な理由を理解できませんでした。私の理解によると、「新規」の作成と呼び出しコンストラクターは順番に実行する必要があり、同期ロックはすべての作業が完了するまで解放されるべきではありません。
ここで何が欠けているか教えてください。
コンストラクターは完了している可能性がありますが、それは、そのコンストラクターに含まれるすべての書き込みが他のスレッドに表示されるようになったことを意味するわけではありません。厄介な状況は、オブジェクトのコンテンツが表示される前に、参照が他のスレッドに表示されるようになる(つまり、スレッドが使用を開始する)場合です。
ビル・ピューの記事が少し光を当てるのにも役立つかもしれません。
個人的には、すべてを機能させるのではなく、ペストのようにロックを再確認することは避けています。
問題のコードはここにあります:
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
これで、コードが記述された順序で実行されると考え続ける限り、これに関する問題を理解することはできません。たとえそうだとしても、今日主流となっている対称型マルチプロセッシングアーキテクチャの複数のプロセッサ(またはコア)間でのキャッシュ同期の問題があります。
Thread1は、たとえばメインメモリへの参照を公開できますが、作成されたオブジェクトinstance
内の他のデータは公開できません。Singleton
Thread2は、一貫性のない状態のオブジェクトを監視します。
Thread2がブロックに入らない限りsynchronized
、キャッシュの同期を行う必要はないため、Thread2はSingleton
、一貫性のある状態を監視することなく、無期限に続行できます。
スレッド 2 は、スレッド 1 が //3 のときにインスタンスが null かどうかを確認します。
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;//4
}
この時点でインスタンスのメモリがヒープから割り当てられ、そのポインタがインスタンス参照に格納されているため、スレッド 2 によって実行される「if ステートメント」は「false」を返します。スレッド 2 がチェックしたときにインスタンスが null ではないため、スレッド 2 は同期ブロックに入らず、代わりに「完全に構築されているが部分的に初期化されているシングルトン オブジェクト」への参照を返すことに注意してください。
コードが書かれた順序で実行されないという一般的な問題があります。Java では、スレッドはそれ自体と一貫性を保つことのみを義務付けられています。あるinstance
行で作成されたnew
は、次の行に進む準備ができている必要があります。他のスレッドにはそのような義務はありません。たとえば、fieldA
is が 1 で 'fieldB' が 2 の場合、スレッド 1 で次のコードに入ります。
fieldA = 5;
fieldB = 10;
スレッド 2 は次のコードを実行します。
int x = fieldA;
int y = FieldB;
1 2、5 2、および 5 10 の xy 値はすべて予想されますが、1 10 (fieldB が fieldA の前に設定および/または取得された) は完全に正当であり、同様に可能性が高いです。そのため、ロックのダブルチェックは、より一般的な問題の特殊なケースであり、複数のスレッドで作業する場合、特にすべてのスレッドが同じフィールドにアクセスする場合は注意が必要です。
言及すべき Java 1.5 からの 1 つの単純なソリューション: マークされたフィールドはvolatile
、参照される直前にメイン メモリから読み取られ、直後に書き込まれることが保証されます。以上が宣言されている場合fieldA
、1 10 の xy 値は不可能です。が volatile の場合、ダブルチェック ロックが機能します。フィールドの使用にはコストがかかりますが、同期よりもコストがかからないため、二重チェックのロックはかなり良いアイデアになります。CPU コアがアイドル状態にある間に多数のスレッドが同期を待機することを回避できるため、これはさらに優れたアイデアです。fieldB
volatile
instance
volatile
しかし、あなたはこれを理解したいと思っています (マルチスレッドについて話せないのであれば)。一方ではタイミングの問題を回避する必要があり、他方ではすべてのスレッドが同期ブロックに入るのを待っている状態でプログラムを停止させないようにする必要があります。そして、それを理解することは非常に困難です。