簡介:
單例模式是一種簡單的設計模式,但是要在程式設計中使用好單例模式,卻需要注意幾個地方。
單例模式意味著在整個系統中,單例類只能有一個例項物件,且需要自行完成例項化,並始終對外提供同一個例項物件。
單例模式實現方式:
餓漢模式:
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 類載入機制 的讀者知道,當第 一次訪 問類中的靜態欄位時,會觸發類載入,並且同一個類只載入一次。靜態內部類也是如此,類載入過程由類載入器負責加鎖,從而保證執行緒安全。