天天看點

java中用枚舉類實作單例模式

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

1. 枚舉寫法簡單

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

枚舉實作:

下面這段代碼就是聲明枚舉執行個體的通常做法,它可能還包含執行個體變量和執行個體方法,但是為了簡單起見,我并沒有使用這些東西,僅僅需要小心的是如果你正在使用執行個體方法,那麼你需要確定線程安全(如果它影響到其他對象的狀态的話)。預設枚舉執行個體的建立是線程安全的,但是在枚舉中的其他任何方法由程式員自己負責。

/**
* Singleton pattern example using Java Enumj
*/
publicenum EasySingleton{
    INSTANCE;
}
           

你可以通過EasySingleton.INSTANCE來通路,這比調用getInstance()方法簡單多了。

double checked locking 實作法:

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

**
* Singleton pattern example with Double checked Locking
*/
publicclass DoubleCheckedLockingSingleton{
     privatevolatile DoubleCheckedLockingSingleton INSTANCE;
 
     privateDoubleCheckedLockingSingleton(){}
 
     publicDoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = newDoubleCheckedLockingSingleton();
                }
            }
         }
         returnINSTANCE;
     }
}
           

你可以使用 DoubleCheckedLockingSingleton.getInstance()來擷取執行個體。

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

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

靜态工廠實作法:

這是我最喜歡的一種方式來實作單例模式,因為單例是靜态的final變量,當類第一次加載到記憶體中的時候就初始化了,是以建立的執行個體固然是thread-safe。

/**
* Singleton pattern example with static factory method
*/
 
publicclass Singleton{
    //initailzed during class loading
    privatestatic final Singleton INSTANCE = newSingleton();
 
    //to prevent creating another instance of Singleton
    privateSingleton(){}
 
    publicstatic Singleton getSingleton(){
        returnINSTANCE;
    }
}
           

你可以調用Singleton.getSingleton()擷取執行個體。

2. 枚舉自己處理序列化

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

//readResolve to prevent another instance of Singleton
    privateObject readResolve(){
        returnINSTANCE;
    }
           

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

3. 枚舉執行個體建立是thread-safe

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

4、采用反射來建立執行個體時.可通過AccessibleObject.setAccessible(),通過反射機制來調用私有構造器.那麼枚舉可以防止這種建立第二個執行個體的情況發生.

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

原文連結: Javarevisited 翻譯: ImportNew.com - 劉志軍

譯文連結: http://www.importnew.com/6461.html