public class Singleton { private volatile static Singleton singleton; //1:volatile修飾 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { //2:減小不要同步,優化性能 synchronized (Singleton.class) { // 3:同步,線程安全 if (singleton == null) { singleton = new Singleton(); //4:建立singleton 對象 } } } return singleton; } } 複製代碼
推薦理由:安全
getSingleton
,纔會初始化signleton
實例。singleton
是否初始化,減小沒必要要的同步開銷。singleton
使用volatile
修飾。爲何要使用volatile修飾?性能優化
雖然已經使用synchronized進行同步,但在第4步建立對象時,會有下面的僞代碼:bash
memory=allocate(); //1:分配內存空間
ctorInstance(); //2:初始化對象
singleton=memory; //3:設置singleton指向剛排序的內存空間
複製代碼
當線程A在執行上面僞代碼時,2和3可能會發生重排序,由於重排序並不影響運行結果,還能夠提高性能,因此JVM是容許的。若是此時僞代碼發生重排序,步驟變爲1->3->2,線程A執行到第3步時,線程B調用getsingleton
方法,在判斷singleton==null
時不爲null
,則返回singleton
。但此時singleton
並還沒初始化完畢,線程B訪問的將是個還沒初始化完畢的對象。當聲明對象的引用爲volatile後,僞代碼的二、3的重排序在多線程中將被禁止!markdown
public class Singleton { private Singleton(){ } public static Singleton getSingleton(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } } 複製代碼
推薦理由:多線程
getSingleton
才初始化Singleton
對象。如何實現線程安全?性能
線程A和線程B同時試圖得到Singleton對象的初始化鎖,假設線程A獲取到了,那麼線程B一直等待初始化鎖。線程A執行類初始化,就算雙重檢查模式中僞代碼發生了重排序,也不會影響線程A的初始化結果。初始化完後,釋放鎖。線程B得到初始化鎖,發現Singleton對象已經初始化完畢,釋放鎖,不進行初始化,得到Singleton對象。優化
在涉及到反射和序列化的單例中,建議使用下文的枚舉類型模式。spa
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 複製代碼
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } 複製代碼
餓漢模式的線程安全一樣經過類加載解決同步問題,但沒有達到懶加載目的。(這裏很是感謝之初z-chu
的指正)線程
public enum Singleton { INSTANCE; public void doSomething(){ //todo doSomething } } 複製代碼
在Joshua Bloch大神的《Effective Java》是推薦該方法的。雖然線程安全,在實際開發中,尚未被普遍採用。由於太過簡潔以至於可讀性較差,尚未在實戰中被普遍推廣。枚舉單例模式的線程安全一樣利用靜態內部類中講到類初始化鎖。枚舉單例模式可以在序列化和反射中保證明例的惟一性。code
高手之間的過招,必選擇枚舉單例模式。
若是以爲文章有用,給文章點個贊,鐵子