1. 程式人生 > >單例模式:層層剖析尋找最高效安全的單例

單例模式:層層剖析尋找最高效安全的單例

問題來源

 什麼是單例?它的運用場景是什麼?

  單例模式是指保證在系統中只存在某類唯一物件。運用場景隨處可見,例如工具類、Spring容器預設new物件等。

  單例模式有幾種實現方式?

  餓漢式、懶漢式、雙重檢查鎖式、內部類式、列舉式。

  推薦使用方式?

  餓漢式、內部類式。

餓漢式

餓漢式顧名思義餓,那麼當應用程式一開始類載入,類的物件立馬例項化載入至JVM。

public class SingletonClass {
    /**
     * 優點:呼叫效率高。
     * 缺點:沒有延遲載入。
     */
    private static SingletonClass instance =new SingletonClass();
    
    public static SingletonClass getInstance(){
        return instance;
    }
}

為什麼呼叫效率高?沒有延遲載入?

  答:假設在高併發的場景下,有10W+併發呼叫,不需要同步處理。可以直接在堆記憶體直接獲取物件不需要任何等待。

    同樣,它沒有延遲載入,如果它是需要消耗很大記憶體的物件,最開始就載入入堆記憶體,而使用者暫時不需要。這樣就會嚴重佔用堆記憶體,影響執行效率。

 懶漢式

導引:腦洞大開的程式設計師們說:上述問題還不簡單,當呼叫的時候在new物件不就行。於是出現了懶漢式的雛形版本。

public class SingletonClass {
    private static SingletonClass instance;
    
    public static SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
}

懶漢式顧名思義懶,就是延遲載入,當被呼叫的時候再例項化。

  問題:如果你是初出茅廬的應屆生寫成這樣,估計面試官也不會追究什麼。如果你是有一年工作年限的程式設計師,估計面試官就會聲討你了。假設,併發數10W+,它就將被蹂躪的不堪入目。那麼我們需要怎麼解決呢?加上同步操作就大功告成。

public class SingletonClass {
    
    //呼叫效率低、延遲載入
    private static SingletonClass instance;
    
    public static synchronized SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
}

問題:從效率維度考慮,估計這樣已經完美了吧?但是,從安全緯度考慮,依然隱隱約約存在問題。如果是接觸過反射、反序列化的同學,我們一起來繼續探討。

/**
 * 通過反射破壞懶漢式單例
 * @author aaron
 */
public class Client {
    public static void main(String[] args) throws Exception {
        SingletonClass clazzOne=SingletonClass.getInstance();
        SingletonClass clazzTwo=SingletonClass.getInstance();
        
        System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
        System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
        
        Class<SingletonClass> clazz=(Class<SingletonClass>)Class.forName("singleton.SingletonClass");
        Constructor<SingletonClass> c=clazz.getConstructor(null);
        c.setAccessible(true);
        SingletonClass clazzThree=c.newInstance();
        SingletonClass clazzFour=c.newInstance();
        System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
        System.out.println("clazzFour-hasCode:"+clazzFour.hashCode());
    }
}

 

public class SingletonClass implements Serializable{
    
    private static SingletonClass instance;
    
    public static synchronized SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
    
    public static void main(String[] args) throws Exception {
        SingletonClass clazzOne=SingletonClass.getInstance();
        SingletonClass clazzTwo=SingletonClass.getInstance();    
        System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
        System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
        
    
        FileOutputStream fos=new FileOutputStream(new File("f:/test.txt"));
        ObjectOutputStream bos=new ObjectOutputStream(fos);
        bos.writeObject(clazzOne);
        bos.close();
        fos.close();
        
        FileInputStream fis=new FileInputStream(new File("f:/test.txt"));
        ObjectInputStream bis=new ObjectInputStream(fis);
        SingletonClass clazzThree=(SingletonClass) bis.readObject();
        System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
    }
}

 

  問題:這麼輕易就被破解了?那怎麼解決呢?

public class SingletonClass implements Serializable{
    
    private static SingletonClass instance;
    
    private SingletonClass(){
        //防止被反射
        if(null!=instance){
            throw new RuntimeException();
        }
    }
    
    public static synchronized SingletonClass getInstance(){
        if(null==instance){
            instance=new SingletonClass();
        }
        return instance;
    }
    
    //當沒有定義這方法時,反序列化預設是重新new物件。
    //反序列化時,如果定義了readResolve()則直接返回此方法指定的物件。而不需要單獨再建立新物件!
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

 雙重檢查鎖與內部類

雙重檢查鎖與內部類的方式:緣由懶漢式、餓漢式要麼存在呼叫效率低或者執行效率低問題。而這兩種方式取前兩者的優點為自己所用。

/**
 * 單例模式-雙重檢查鎖
 * @author aaron
 */
public class SingletonClass{
    private static SingletonClass instance;
    
    public static  SingletonClass getInstance(){
        if(null==instance){
            synchronized (SingletonClass.class) {
                if(instance==null){
                    instance=new SingletonClass();
                }
            }
        }
        return instance;
    }
}

問題:緣由JVM對於此種方式的同步控制,並不穩定,當高併發的時候,可能會出現問題,並不推薦使用這種方式。理論上來說,它是不存在問題的。

/**
 * 單例模式-內部類的方式
 * @author aaron
 */
public class SingletonClass{
    
    private static class InnerClass{
        public static SingletonClass instance=new SingletonClass(); 
    }
    
    public static SingletonClass getInstance(){
        return InnerClass.instance;
    }
}
/**
 * 單例模式-列舉的方式
 * @author aaron
 */
public enum SingletonClass{
    INSTANCE
}