標籤:方法 亂序 讀取 空間 let 單例 靜態 gets 推薦
一、餓漢式
這種方式基於classloder機制避免了多線程的同步問題
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){} 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
這種實現方式適合單例佔用記憶體比較小,在初始化時就會被用到的情況。但是如果單例佔用的記憶體比較大,或單例只是在某個特定情境下才會用到,使用餓漢模式就不合適了,這時候就需要用到懶漢模式進行消極式載入。
二、懶漢式單例
使用靜態內部類
這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而執行個體化instance
並且由於靜態內部類只會被載入一次,所以這種寫法也是安全執行緒的:
1 public class Singleton { 2 private static class SingletonHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 private Singleton (){} 6 public static final Singleton getInstance() { 7 return SingletonHolder.INSTANCE; 8 } 9 }
雙重檢查鎖定
在加鎖前多進行一次null檢查就可以減少絕大多數的加鎖操作,執行效率提高的目的也就達到了。
如果不使用volatile,由於指令重排最佳化的存在,導致初始化Singleton和將對象地址賦給singleton欄位的順序是不確定的。在某個線程建立單例對象時,在構造方法被調用之前,就為該對象分配了記憶體空間並將對象的欄位設定為預設值。此時就可以將分配的記憶體位址賦值給singleton欄位了,然而該對象可能還沒有初始化。若緊接著另外一個線程來調用getSingleton,取到的就是狀態不正確的對象,程式就會出錯。
1 public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
volatile這個關鍵字就是可見度。可見度指的是在一個線程中對該變數的修改會馬上由工作記憶體(Work Memory)寫回主記憶體(Main Memory),所以會馬上反應在其它線程的讀取操作中。順便一提,工作記憶體和主記憶體可以近似理解為實際電腦中的快取和主存,工作記憶體是線程獨享的,主存是線程共用的。
volatile的第二層語義是禁止指令重排序最佳化。大家知道我們寫的代碼(尤其是多線程代碼),由於編譯器最佳化,在實際執行的時候可能與我們編寫的順序不同。編譯器只保證程式執行結果與原始碼相同,卻不保證實際指令的順序與原始碼相同。這在單線程看起來沒什麼問題,然而一旦引入多線程,這種亂序就可能導致嚴重問題。volatile關鍵字就可以從語義上解決這個問題。
使用枚舉
使用枚舉除了安全執行緒和防止反射強行調用構造器之外,還提供了自動序列化機制,防止還原序列化的時候建立新的對象。因此,Effective Java推薦儘可能地使用枚舉來實現單例。
1 public enum Singleton { 2 INSTANCE; 3 private String name; 4 public String getName(){ 5 return name; 6 } 7 public void setName(String name){ 8 this.name = name; 9 }10 }
餓漢式與懶漢式區別
1、餓漢式在類建立的同時就執行個體化一個靜態對象出來,不管之後會不會使用這個單例,都會佔據一定的記憶體,但是相應的,在第一次調用單例時速度也會更快,因為其資源已經初始化完成。
2、懶漢式顧名思義,會消極式載入,在第一次使用該單例的時候才會執行個體化對象出來,第一次調用時要做初始化,如果要做的工作比較多,效能上會有些延遲,之後就和餓漢式一樣了。
如果初始化工作比較多,佔用資源少,調用次數多,建議使用餓漢式,在訪問的時候可以加快調用的速度。
如果某個單例使用的次數少,並且建立單例消耗的資源較多,那麼就需要實現單例的按需建立,這個時候使用懶漢模式就是一個不錯的選擇。
Java單例模式