為什麼java中用列舉實現單例模式會更好

芥末無疆發表於2018-02-22

程式碼簡潔

  這是迄今為止最大的優點,如果你曾經在Java5之前寫過單例模式程式碼,那麼你會知道即使是使用雙檢鎖你有時候也會返回不止一個例項物件。雖然這種問題通過改善java記憶體模型和使用volatile變數可以解決,但是這種方法對於很多初學者來說寫起來還是很棘手。相比用 synchronization的雙檢鎖實現方式來說,列舉單例就簡單多了。你不相信?比較一下下面的雙檢鎖實現程式碼和列舉實現程式碼就知道了。

用列舉實現的單例:

這是我們通常寫列舉單例的方式,它可能包含例項變數和例項方法,但是簡單來說我什麼都沒用,需要注意的是如果你使用例項方法,你就需要確保方法的執行緒安全性,避免它會影響物件的狀態。通常情況下列舉裡面建立例項是執行緒安全的,但是其它的方法就需要程式設計者自己去考慮了。

public enum EasySingleton{
    INSTANCE;
}

程式碼就這麼簡單,你可以使用EasySingleton.INSTANCE呼叫它,比起你在單例中呼叫getInstance()方法容易多了。

用雙檢索實現單例:

下面的程式碼是用雙檢索實現單例模式的例子,在這裡getInstance()方法檢查了兩次來判斷INSTANCE是否為null,這就是為什麼叫雙檢索的原因,記住雙檢索在java5之前是有問題的,但是java5在記憶體模型中有了volatile變數之後就沒問題了。

public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;
  
     private DoubleCheckedLockingSingleton(){}
  
     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

你可以訪問DoubleCheckedLockingSingleTon.getInstance()來獲得例項物件。

現在看看二者建立一個懶載入執行緒安全的單例需要的程式碼數量。
使用列舉單例模式你只需要一行程式碼搞定因為列舉例項的建立是執行緒安全的。

你可能會說比起使用雙檢索方法還有更好的方法實現單例模式,但是任何一種方法都有它的利和弊,就像我下面例子中展示的我很喜歡的一種在類載入期間初始化靜態域的單例實現方式,但是要記住這不是一種懶載入單例方式。

用靜態工廠方法實現單例:

這是java中我比較喜歡的一種實現單例模式的方法,由於單例例項是static和final的,當類第一次被載入到記憶體它就例項化了,所以這種例項的建立方式是執行緒安全的。

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();
  
    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

你可以呼叫Singleton.getInstance()方法來獲得例項物件。

2)列舉單例可以自己處理序列化

傳統的單例模式的另外一個問題是一旦你實現了serializable介面,他們就不再是單例的了,因為readObject()方法總是返回一個 新的例項物件,就像java中的構造器一樣。你可以使用readResolve()方法來避免這種情況,通過像下面的例子中這樣用單例來替換新建立的實 例:

private Object readResolve(){
        return INSTANCE;
}

如果你的單例類包含狀態的話就變的更復雜了,你需要把他們置為transient狀態,但是用列舉單例的話,序列化就不要考慮了。

3)列舉單例是執行緒安全的

就像第一點提到的,由於列舉例項的建立預設就是執行緒安全的,你不需要擔心雙檢鎖問題。

總結:通過提供序列化和執行緒安全並且幾行程式碼搞定,說明列舉單例模式是java5之後建立單例最好的方法。你仍然可以使用其它你感覺很流行的方式來建立單例,但是我還是要找一個能夠使我信服的觀點讓我不去使用列舉作為單例,如果你有,請告訴我!


相關文章