簡介: 單例模式是一種簡單的設計模式,但是要在程式設計中使用好單例模式,卻需要註意幾個地方。 單例模式意味著在整個系統中,單例類只能有一個實例對象,且需要自行完成實例化,並始終對外提供同一個實例對象。 單例模式實現方式: 餓漢模式: 懶漢模式(單線程版): 上述懶漢模式有延遲載入的意思,但是沒有考慮 ...
簡介:
單例模式是一種簡單的設計模式,但是要在程式設計中使用好單例模式,卻需要註意幾個地方。
單例模式意味著在整個系統中,單例類只能有一個實例對象,且需要自行完成實例化,並始終對外提供同一個實例對象。
單例模式實現方式:
餓漢模式:
public class Singleton { //餓漢模式是最簡單的實現方式,在類載入時就創建單例類對象 private static final Singleton instance = new Singleton(); //私有化構造方法,防止外界創建對象 private Singleton(){} public static Singleton newInstance(){ //返回唯一的實例對象 return instance; } }
懶漢模式(單線程版):
public class Singleton { private static Singleton instance = null; //私有化構造方法,防止外界創建對象 private Singleton(){} public static Singleton newInstance(){ // 在需要的時候才去創建的單例對象,如採羊例對象已經創建,再次調用 ηewinstance()方法時 // 將不會重新創建新的單例對象,而是直接返回之前創建的單例對象 if (null == instance){ instance = new Singleton(); } //返回唯一的實例對象 return instance; } }
上述懶漢模式有延遲載入的意思,但是沒有考慮到多線程的情況,在多線程的場景中,出現多個線程同時調用newInstance方法創建單例對象,從而導致系統中出現多個單例類的實例,顯然是不符合要求的。
懶漢模式(多線程加鎖版):
public class Singleton { private static Singleton instance = null; //私有化構造方法,防止外界創建對象 private Singleton(){} public static synchronized Singleton newInstance(){ // 在需要的時候才去創建的單例對象,如採羊例對象已經創建,再次調用 ηewinstance()方法時 // 將不會重新創建新的單例對象,而是直接返回之前創建的單例對象 if (null == instance){ instance = new Singleton(); } //返回唯一的實例對象 return instance; } }
上述加鎖設計雖然解決了多線程安全的問題,但是單例在系統中只有一個實例,每次取實例都得進行加鎖解鎖操作,這會成為系統的性能瓶頸。
懶漢模式(雙重檢測鎖方式):錯誤實現
public class Singleton { private static Singleton instance = null; //私有化構造方法,防止外界創建對象 private Singleton() { } public static Singleton newInstance() { if (null == instance) {//第一次檢測 synchronized (Singleton.class) {//加鎖 if (null == instance) {//第二次檢測 instance = new Singleton(); } } } return instance; } }
由於指令重排優化,可能會導致初始化單例對象和將該對象地址賦值給 instance 欄位的順 序與上面 Java 代碼中書 寫 的順序不同 。 例如,線程 A 在創建單例對象時,在構造方法被調用 之前,就為該對象分配了記憶體空間並將對象的宇段設置為預設值。此時線程 A 就可以將分配的內 存地址賦值給 instance 宇段了,然而該對象可能還沒有初始化。線程 B 來調用 newInstance()方法,得 到的就 是未初始 化 完全 的單例對象,這就 會導致系統出 現異常行為 。
為瞭解決該問題,我們可以使用 volatile關鍵字修飾 instance欄位。 volatile關鍵字的一個語義就是禁止指令的重排序優化,從而保證 instance 欄位被初始化時,單例對象己經被完全初始化 。
懶漢模式(雙重檢測鎖方式 + volatile):
public class Singleton { private static volatile Singleton instance = null; //私有化構造方法,防止外界創建對象 private Singleton() { } public static Singleton newInstance() { if (null == instance) {//第一次檢測 synchronized (Singleton.class) {//加鎖 if (null == instance) {//第二次檢測 instance = new Singleton(); } } } return instance; } }
靜態內部類方式(推薦):
public class Singleton { private static class SingletonHolder { private static final Singleton instance = new Singleton(); } //私有化構造方法,防止外界創建對象 private Singleton() { } public static Singleton newInstance() { return SingletonHolder.instance; } }
熟悉 Java 類載入機制 的讀者知道,當第 一次訪 問類中的靜態欄位時,會觸發類載入,並且同一個類只載入一次。靜態內部類也是如此,類載入過程由類載入器負責加鎖,從而保證線程安全。