單例模式中為什麼用列舉更好

林加欣發表於2017-11-29

列舉單例(Enum Singleton)是實現單例模式的一種新方式,儘管單例模式在java中已經存在很長時間了,但是列舉單例相對來說是一種比較新的概念,列舉這個特性是在Java5才出現的,這篇文章主要講解關於為什麼我們應該使用列舉來實現單例模式,它與傳統方式實現的單例模式相比較又有哪些優勢?

1. 列舉寫法簡單

寫法簡單這是它最大的優點,如果你先前寫過單例模式,你應該知道即使有DCL(double checked locking) 也可能會建立不止一個例項,儘管在Java5這個問題修復了(jdk1.5在記憶體模型上做了大量的改善,提供了volatile關鍵字來修飾變數),但是仍然對新手來說還是比較棘手。對比通過double checked locking 實現同步,列舉單例那實在是太簡單了。如果你不相信那麼對比下面程式碼,分別為傳統的用double checked locking實現的單例和列舉單例。

列舉實現:

下面這段程式碼就是宣告列舉例項的通常做法,它可能還包含例項變數和例項方法,但是為了簡單起見,我並沒有使用這些東西,僅僅需要小心的是如果你正在使用例項方法,那麼你需要確保執行緒安全(如果它影響到其他物件的狀態的話)。預設列舉例項的建立是執行緒安全的,但是在列舉中的其他任何方法由程式設計師自己負責。

1
2
3
4
5
6
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

你可以通過EasySingleton.INSTANCE來訪問,這比呼叫getInstance()方法簡單多了。

double checked locking 實現法:

下面程式碼就是用double checked locking 方法實現的單例,這裡的getInstance()方法要檢查兩次,確保是否例項INSTANCE是否為null或者已經例項化了,這也是為什麼叫double checked locking 模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Singleton pattern example with Double checked Locking
*/
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()來獲取例項。

從建立一個lazy loaded thread-safe單例來看,它的程式碼行數與列舉相比,後者可以全部在一行內完成,因為列舉建立的單例在JVM層面上也能保證例項是thread-safe的。

人們可能會爭論有更好的方式去寫單例用來替換duoble checked locking 方法,但是每種方法有他自己的優點和缺點,象我很多時候更願初始化通過類載入靜態欄位,如下所示,但是記住他不是lazy loaded形式的單例。

靜態工廠實現法:

這是我最喜歡的一種方式來實現單例模式,因為單例是靜態的final變數,當類第一次載入到記憶體中的時候就初始化了,所以建立的例項固然是thread-safe。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Singleton pattern example with static factory method
*/
 
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.getSingleton()獲取例項。

2. 列舉自己處理序列化

傳統單例存在的另外一個問題是一旦你實現了序列化介面,那麼它們不再保持單例了,因為readObject()方法一直返回一個新的物件就像java的構造方法一樣,你可以通過使用readResolve()方法來避免此事發生,看下面的例子:

1
2
3
4
//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

這樣甚至還可以更復雜,如果你的單例類維持了其他物件的狀態的話,因此你需要使他們成為transient的物件。但是列舉單例,JVM對序列化有保證。

3. 列舉例項建立是thread-safe

正如在第一條中所說的,因為建立列舉預設就是執行緒安全的,你不需要擔心double checked locking。

總結:列舉單例有序列化和執行緒安全的保證,而且只要幾行程式碼就能實現是單例最好的的實現方式,不過你仍然可以使用其它的方式來實現單例,但是我仍然得不到一個更有信服力的原因不去使用列舉。如果你有的話,不妨告訴我。

原文連結: Javarevisited 翻譯: ImportNew.com 劉志軍
譯文連結: http://www.importnew.com/6461.html
轉載請保留原文出處、譯者和譯文連結。]

相關文章