Lazybonesシングルインスタンス(コンピューティング)
まずコードを見てみましょう。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
問題分析
我々はすべてのこの種のシングルトンは、スレッドセーフではないことを知っています。理由は、AとBの2つのスレッドがgetInstanceメソッドに入っている場合、この場合、ステートメントの判断が真である場合は、2つのシングルトンオブジェクトの新しいので、これはシングルトンパターンに沿っていないためです。したがって、単純な怠け者は、スレッドの安全性を保証することはできません。
遅延シングルトンの最適化
この理由は、2つのスレッドが同時にメソッドにアクセスしているためなので、メソッドにsynchronizedロックをかければ終わりです。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
最適化は、同期を行うにはgetInstanceメソッドの上に、つまり、非常に簡単ですが、我々はすべての同期がロック重量であることを知っている、パフォーマンスがより非効率的である、スレッドのブロッキングにつながる、その結果、プログラムのパフォーマンスが低下します。だからどのようにそれを解決するために、DCLが登場
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
コードは完璧に見えます。
- 最初のシングルトンがNULLでないことをチェックすれば、次のロック動作を行う必要がなくなり、プログラムのパフォーマンスが大幅に向上します;
- 最初のシングルトンがnullの場合、複数のスレッドが同時に判定していても、synchronizedによりオブジェクトを生成できるのは1つのスレッドだけです;
- 最初のスレッドは、シングルトンオブジェクトの完了の作成後にロックを取得するときは、2番目の判断シングルトンで他がNULLであってはならないし、直接良いシングルトンオブジェクトを作成しているに戻ります;
オブジェクトのインスタンス化は3つのステップに分かれています:
- メモリ空間の割り当て
- オブジェクトの初期化
- メモリ空間のアドレスを対応する参照に割り当てます。
その3つのステップのうち、2と3の順番を入れ替えたんですよ!!!
ステップ2と3の順番が入れ替わった場合は、コードに従って分析を続けます。
スレッドAはまずメモリ空間を確保し、次にメモリ空間のアドレスをシングルトンに割り当てます。
この時点で、スレッドBもメソッドに入ります。スレッドBはまずメモリ空間を確保し、次にコードに基づいてシングルトンにアクセスします。コンパイラとプロセッサは、並列性に対する制御の相関の影響を克服するために推測実行を使用することを覚えておいてください。
ここで問題が発生します。スレッドBが判断した条件が真であり、同時にスレッドAがオブジェクトの初期化を開始したところです!
以上の分析に基づき、最終的に問題となるのは
singleton = new Singleton();
このコード行 原因によって、解決策は次の2つのうちの1つになります。
- 初期化フェーズのステップ2と3では、並べ替えを行うことはできません。
- 初期化フェーズのステップ2と3で並び替えを許可しますが、他のスレッドには並び替えを「見せない」ようにします。
揮発性に基づく遅延シングルトンの解決
この解決策は実はとても簡単で、変数singletonをvolatileに設定するだけです。
public class Singleton {
//volatileキーワードによるセキュリティ
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
揮発性の並び替えルールの見直し継続
- 最初の操作が揮発性読み取りの場合、2 番目の操作が何であるかに関係なく、並べ替えはできません。この動作により、揮発性読み出しの後の操作は、コンパイラによって揮発性読み出しに並び替えられないようになります;
- 2番目の操作が揮発性書き込みである場合、最初の操作に関係なく、並び替えはできません。これにより、揮発性書き込みの後にコンパイラによって揮発性書き込みが再順序付けされないことが保証されます;
- 最初の操作が揮発性書き込みで、2番目の操作が揮発性読み出しの場合、並び替えはできません。
シングルトンがvolatile宣言されている場合、オブジェクトを初期化し、対応する参照にメモリ空間のアドレスを割り当てても、順序が入れ替わることはありません。
スタディー・ソース The Art of Concurrent Programming in Java
個人的な意見です。議論すること自由に感じなさい、個人的なブログ
JAVAJAVA concurrencyhappens-before
JAVA並行処理ハペンズビフォー
JAVA 同時並行並べ替え
JMM分析によるJAVA同時実行の揮発性
JAVAコンカレント最終フィールド
DCLのJAVA並行性詳細分析
JAVA コンカレント・プログラミングの基礎




