傳統的兩私有一公開(私有構造方法、私有靜態實例(懶實例化/直接實例化)、公開的靜態獲取方法)涉及線程安全問題(即便有多重檢查鎖也能夠經過反射破壞單例), 目前最爲安全的實現單例的方法是經過內部靜態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 方法實現的單例,這裏的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對序列化有保證。
正如在第一條中所說的,由於建立枚舉默認就是線程安全的,你不須要擔憂double checked locking。
總結:枚舉單例有序列化和線程安全的保證,並且只要幾行代碼就能實現是單例最好的的實現方式,不過你仍然可使用其它的方式來實現單例,可是我仍然得不到一個更有信服力的緣由不去使用枚舉。若是你有的話,不妨告訴我。