設計模式之禪讀書筆記-單例模式你真的掌握了嗎?

weixin_34320159發表於2017-06-05

date: 2017-04-10 23:21:08

單例模式的定義

單例模式是以個比較簡單的模式,其定義如下:
Ensure a class has only one instance, and provide a global point of access to it.(確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。)

優點:
單例模式主要是為了避免因為建立了多個例項造成資源的浪費,且多個例項由於多次呼叫容易導致結果出現錯誤,而使用單例模式能夠保證整個應用中有且只有一個例項。可以在系統設定全域性的訪問點,優化和共享資源訪問。

缺點:
單例模式一般沒有介面,擴充困難,除了修改程式碼基本沒有第二種途徑可以實現;對測試不利,單例模式沒有完成,是不能進行測試的;單例模式與單一職責原則有衝突。

理解懶漢與餓漢:
懶漢和餓漢的本質區別,就是例項化物件的時機。

“懶漢式”是在你真正用到的時候才去建這個單例物件。
“餓漢式”類載入的時候,就把單例的初始化完成了。

如何寫好單例模式?

不好的寫法:

第一種:懶漢式。多執行緒情況下,多個執行緒可以同時通過
If(singleton == null) 建立多個Singleton(),所以是執行緒不安全的。

public Class Singleton{

    private static Singleton instance;

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

}

第二種 懶漢,執行緒安全。在getInstance()加上synchronized,實現同步,此時執行緒安全,但是在方法加上synchronized後,在一個執行緒執行getInstance()的,其他執行緒都被阻塞了,所以效能比較低下。

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

下面我們看比較高效+執行緒安全的寫法。

第一種: 雙重檢查鎖定(double-checked locking) ,這種寫法在第一次初始化之後,其他執行緒再來getInstance的時候會省掉很多因為同步帶來的的效能消耗。

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

但是,這個版本的寫法在指令重排的情況下會出錯
可以在 private static Singleton instance 加入 volatile 改為
private static volatile Singleton instance

volatile:這個關鍵字有兩層語義。第一層語義相信大家都比較熟悉,就是可見性。可見性指的是在一個執行緒中對該變數的修改會馬上由工作記憶體(Work Memory)寫回主記憶體(Main Memory),所以會馬上反應在其它執行緒的讀取操作中。順便一提,工作記憶體和主記憶體可以近似理解為實際電腦中的快取記憶體和主存,工作記憶體是執行緒獨享的,主存是執行緒共享的。volatile的第二層語義是禁止指令重排序優化。

這樣子應該可以了吧?

然而,禁止指令重排優化這條語義在jdk1.5以後才能正確工作
關於這個寫法存在的問題,涉及到的知識比較深入了,現在暫時不做深究,感興趣的朋友可以嘗試閱讀《深入理解JVM》裡面類載入的相關內容。
此外DCL還有其他的寫法,比如從synchronized一個類改成一個static 的Object物件。

第二種:餓漢式。缺點是它不是一種懶載入模式,單例會在載入類後一開始就被初始化。

public class Singleton{
//類載入時就初始化
    private static final Singleton instance = new Singleton();

    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

第三種:Effective Java 推薦單例用法:靜態內部類法。這種寫法仍然使用JVM本身機制保證了執行緒安全問題;由於 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取例項的時候不會進行同步,沒有效能缺陷。

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

相關文章