いくつかの単例モード

4417 ワード

面接のときによく聞かれる質問ですが、Singleton Patternというパターンを書いてください.よし、書くなら書くが、これは容易ではない.ついでに1つ書きます.
public final class EagerSingleton   
{   
    private static EagerSingleton singObj = new EagerSingleton();   
   
    private EagerSingleton(){   
    }   
   
    public static EagerSingleton getSingleInstance(){   
       return singObj; 
    }   
} 

 
この書き方はいわゆる飢餓モードであり,各オブジェクトは使用されない前に初期化されている.これは潜在的なパフォーマンスの問題をもたらす可能性があります.もしこのオブジェクトが大きいとしたら?このオブジェクトを使用する前に、メモリにロードするのは大きな無駄です.この場合,以上のコードを改良し,遅延ロード(Lazy−load Singleton)という新しい設計思想を用いることができる.
public final class LazySingleton   
{   
    private static LazySingleton singObj = null;   
   
    private LazySingleton(){   
    }   
   
    public static LazySingleton getSingleInstance(){   
        if(null == singObj ) singObj = new LazySingleton(); 
          return singObj; 
    }   
}   

 
この書き方はいわゆる怠け者モードです.遅延ロードを使用して、オブジェクトが使用されない前に初期化されないことを保証します.しかし、面接官は通常、新しい質問をしていじめる.彼は「この書き方はスレッドが安全ですか」と聞くだろう.答えは必然的に:安全ではありません.これは、複数のスレッドが9行目まで同時に実行され、singObjがnullであると判断し、同時に初期化されるためである.したがって、このコードスレッドをどのように安全にするかという問題に直面しています.簡単ですが、その方法の前にSynchronizedを付けておけばOKです.
public final class ThreadSafeSingleton   
{   
    private static ThreadSafeSingleton singObj = null;   
   
    private ThreadSafeSingleton(){   
    }   
   
    public static Synchronized ThreadSafeSingleton getSingleInstance(){   
        if(null == singObj ) singObj = new ThreadSafeSingleton(); 
            return singObj; 
    }   
} 

ここまで書くと、面接官はずるい目であなたを見て、この書き方に性能の問題はありませんか?答えはあるに違いない!同期の代価は必然的にある程度プログラムの同時度を低下させる.では、スレッドが安全である一方で、高い同時性を持つ方法はありませんか.スレッドが安全でない原因は,実際にはオブジェクトを初期化する際であることを観察したので,同期の粒度を低減し,オブジェクトを初期化する際のみ同期を行う方法を考えることができる.ここでは,ダブルチェックロック(Double-Checked Lock)という新しい設計思想を提案する必要がある.
public final class DoubleCheckedSingleton   
{   
    private static DoubleCheckedSingletonsingObj = null;   
   
    private DoubleCheckedSingleton(){   
    }   
   
    public static DoubleCheckedSingleton getSingleInstance(){   
        if(null == singObj ) { 
              Synchronized(DoubleCheckedSingleton.class){ 
                     if(null == singObj) 
                           singObj = new DoubleCheckedSingleton(); 
              } 
         } 
       return singObj; 
    }   
}   

この書き方では,新しいオブジェクトをロードして同期するだけで,ロードが完了した後,他のスレッドは9行目でロックをスキップする代価を15行目のコードに直接判断できるようになった.同時性をよくします.
これで、上記の書き方はLazy-Loadを実現する一方で、同時性の良いスレッドの安全を実現し、すべてが完璧に見えます.これは、面接官があなたの答えに満足してうなずくかもしれません.しかし、あなたはこの时に言って、実はこのような书き方はやはり问题があります!!問題はどこですか.スレッドAが9行目まで実行されたと仮定すると、オブジェクトが空であると判断し、スレッドAが12行目まで実行されてこのオブジェクトを初期化するが、初期化には時間がかかるが、このオブジェクトのアドレスはすでに存在する.このときスレッドBも9行目まで実行され,空ではないと判断し,そのまま15行にジャンプしてこのオブジェクトを得た.でも、この相手は
完全に初期化されていません!初期化されていない完全なオブジェクトを得るのに何の役にも立たない!!このDouble-Checked Lockについての議論は多く、現在はAnti-Patternであることが公認されており、使用は推奨されていません!だから面接官があなたのこの返事を聞いたら、彼はHoldされるのではないでしょうか.
では、もっと良い書き方はありますか?あります!ここではまた新しいモデルを提案します
Initialization on Demand Holder.この方法は内部クラスを用いてオブジェクトのロードを遅延させ,この内部クラスを初期化する際にJLS(Java Language Sepcification)がこのクラスのスレッドの安全を保証する.この書き方の最大の美しさは、Java仮想マシンのメカニズムを完全に使用して同期保証を行い、同期のキーワードがないことです.
public class Singleton    
{    
    private static class SingletonHolder    
    {    
        public final static Singleton instance = new Singleton();    
    }    
   
    public static Singleton getInstance()    
    {    
        return SingletonHolder.instance;    
    }    
}  
これで、本文は終わります.いくつかのリンクFor your referenceを提供します.
Double-Checked Lock:
http://en.wikipedia.org/wiki/Double-checked_locking
Initialzation on Demand Holder: 
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_
idiom