單例模式限制類的實例和確保java類在java虛擬機中只有一個實例的存在。java
單例類必須提供一個全局的訪問來獲取類的實例。設計模式
單例模式用來日誌,驅動對象,緩存和線程池。緩存
單例設計模式也用在其餘設計模式,例如抽象工廠,建造者,原型,門面等設計模式。安全
單例模式還用在覈心java中,例如java.lang.Runtime, java.awt.Desktop多線程
爲了實現Singleton模式,咱們有不一樣的方法,但它們都有如下共同的概念。併發
volatile雙重檢查鎖機制
顧名思義,餓漢式就是第一次引用該類的時候就建立實例對象,而不論是否須要。代碼以下:ide
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() {} public static Singleton getSignleton(){ return singleton; } }
優缺點:這樣作的好處是代碼簡單,可是沒法作到延遲加載。可是不少時候咱們但願可以延遲加載,從而減少負載,因此就有了下面的懶漢式;高併發
單線程寫法
這種寫法是最簡單的,由私有構造器和一個公有靜態工廠方法構成,在工廠方法中對singleton進行null判斷,若是是null就new一個出來,最後返回singleton對象。
這種方法能夠實現延時加載,可是有一個致命弱點:線程不安全。若是有兩條線程同時調用getSingleton()方法,就有很大可能致使重複建立對象。
性能
public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton() { if(singleton == null) { singleton = new Singleton(); } return singleton; } }
線程安全寫法
這種寫法考慮了線程安全,將對singleton的null判斷以及new的部分使用synchronized
進行加鎖。同時,對singleton對象使用volatile
關鍵字進行限制,保證其對全部線程的可見性,而且禁止對其進行指令重排序優化
。如此便可從語義上保證這種單例模式寫法是線程安全的。注意,這裏說的是語義上,實際使用中仍是存在小坑的,會在後文寫到。學習
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } return singleton; } }
雖然上面這種寫法是能夠正確運行的,可是其效率低下,仍是沒法實際應用。由於每次調用getSingleton()方法,都必須在synchronized這裏進行排隊,而真正遇到須要new的狀況是很是少的。因此,就誕生了第三種寫法:
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
這種寫法被稱爲「雙重檢查鎖」,顧名思義,就是在getSingleton()方法中,進行兩次null檢查。看似畫蛇添足,但實際上卻極大提高了併發度,進而提高了性能。爲何能夠提升併發度呢?就像上文說的,在單例中new的狀況很是少,絕大多數都是能夠並行的讀操做。所以在加鎖前多進行一次null檢查就能夠減小絕大多數的加鎖操做,執行效率提升的目的也就達到了;
雙重檢查鎖機制的坑
那麼,這種寫法是否是絕對安全呢?前面說了,從語義角度來看,並無什麼問題。可是其實仍是有坑。
工做內存是線程獨享的,主存是線程共享的
。注意,禁止指令重排優化這條語義直到jdk1.5之後才能正確工做。此前的JDK中即便將變量聲明爲volatile也沒法徹底避免重排序所致使的問題。因此,在jdk1.5版本前,雙重檢查鎖形式的單例模式是沒法保證線程安全的。
那麼,有沒有一種延時加載,而且能保證線程安全的簡單寫法呢?咱們能夠把Singleton實例放到一個靜態內部類中,這樣就避免了靜態實例在Singleton類加載的時候就建立對象,而且因爲靜態內部類只會被加載一次,因此這種寫法也是線程安全的:
public class Singleton { private static class Holder { private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; } }
可是,上面提到的全部實現方式都有兩個共同的缺點:
Serializable、transient、readResolve()
)來實現序列化,不然每次反序列化一個序列化的對象實例時都會建立一個新的實例。固然,還有一種更加優雅的方法來實現單例模式,那就是枚舉寫法:
public enum SingleEnum { NEW_INSTANCE { @Override protected void doSomething() { System.out.println("----業務方法調用----"); } }; SingleEnum() { } /** * 業務方法定義 */ protected abstract void doSomething(); public static void main(String[] args) { SingleEnum.NEW_INSTANCE.doSomething(); } }
使用枚舉除了線程安全和防止反射強行調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,Effective Java推薦儘量地使用枚舉來實現單例。
代碼沒有一勞永逸的寫法,只有在特定條件下最合適的寫法。在不一樣的平臺、不一樣的開發環境(尤爲是jdk版本)下,天然有不一樣的最優解(或者說較優解)。
好比枚舉,雖然Effective Java中推薦使用,可是在Android平臺上倒是不被推薦的。在這篇Android Training中明確指出:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
再好比雙重檢查鎖法,不能在jdk1.5以前使用,而在Android平臺上使用就比較放心了(通常Android都是jdk1.6以上了,不只修正了volatile的語義問題,還加入了很多鎖優化,使得多線程同步的開銷下降很多)。
最後,無論採起何種方案,請時刻牢記單例的三大要點:
參考資料
《Effective Java(第二版)》 《深刻理解Java虛擬機——JVM高級特性與最佳實踐(第二版)》