單例模式中爲何用枚舉更好

一、概述

傳統的兩私有一公開(私有構造方法、私有靜態實例(懶實例化/直接實例化)、公開的靜態獲取方法)涉及線程安全問題(即便有多重檢查鎖也能夠經過反射破壞單例), 目前最爲安全的實現單例的方法是經過內部靜態enum的方法來實現,由於JVM會保證enum不能被反射而且構造器方法只執行一次。java

二、代碼擼一發

/**
 * 使用枚舉的單例模式
 *
 * @author yzl
 * @see [相關類/方法](可選)
 * @since [產品/模塊版本] (可選)
 */
public class EnumSingleton{
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    
    private static enum Singleton{
        INSTANCE;
        
        private EnumSingleton singleton;
        //JVM會保證此方法絕對只調用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}

三、 枚舉寫法簡單

寫法簡單這是它最大的優勢,若是你先前寫過單例模式,你應該知道即便有DCL(double checked locking) 也可能會建立不止一個實例,儘管在Java5這個問題修復了(jdk1.5在內存模型上作了大量的改善,提供了volatile關鍵字來修飾變量),可是仍然對新手來講仍是比較棘手。對比經過double checked locking 實現同步,枚舉單例那實在是太簡單了。若是你不相信那麼對比下面代碼,分別爲傳統的用double checked locking實現的單例和枚舉單例。程序員

四、枚舉實現:

下面這段代碼就是聲明枚舉實例的一般作法,它可能還包含實例變量和實例方法,可是爲了簡單起見,我並無使用這些東西,僅僅須要當心的是若是你正在使用實例方法,那麼你須要確保線程安全(若是它影響到其餘對象的狀態的話)。默認枚舉實例的建立是線程安全的,可是在枚舉中的其餘任何方法由程序員本身負責。安全

/**
* 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 模式。code

/**
* 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。get

/**
* 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()獲取實例。同步

七、 枚舉本身處理序列化

傳統單例存在的另一個問題是一旦你實現了序列化接口,那麼它們再也不保持單例了,由於readObject()方法一直返回一個新的對象就像java的構造方法同樣,你能夠經過使用readResolve()方法來避免此事發生,看下面的例子:

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

這樣甚至還能夠更復雜,若是你的單例類維持了其餘對象的狀態的話,所以你須要使他們成爲transient的對象。可是枚舉單例,JVM對序列化有保證。

八、枚舉實例建立是thread-safe

正如在第一條中所說的,由於建立枚舉默認就是線程安全的,你不須要擔憂double checked locking。

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

相關文章
相關標籤/搜索