一、平時使用的軟件中,例如 回收站、線程池、文件系統等,都只有一個實例,這些都是單例模式的典型應用。
單例模式:確保某個類只有一個實例,並提供一個全局訪問點來訪問這個實例。
單例模式有三個要點:
1. 某個類只能有一個實例
2. 必須自行創建這個實例
3. 必須自行向整個系統提供這個實例。
以上三個要點提示着我們的代碼編寫需要注意,構造函數必須私有,否則在其他類中便可以調用構造函數創建實例,難以保證實例的唯一性。
二、單例模式分為餓漢模式和懶漢模式
//餓漢模式:(線程安全) public class Singleton1 { // 靜態私有成員變量 private static Singleton1 instance = new Singleton1(); // 私有構造函數 private Singleton1() { } // 靜態公有工廠方法,返回唯一實例 public static Singleton1 getInstance() { return instance; } }
// 懶漢模式:(線程不安全,需要通過雙重檢查鎖定機制控制) public class Singleton2 { // 靜態私有成員變量 private static Singleton2 instance = null; // 私有構造函數 private Singleton2() { } // 靜態公有工廠方法,判斷成員變量是否為空,不為空則實例化 public static Singleton2 getInstance() { if(instance == null) instance = new Singleton2(); return instance; } }
優缺點:
餓漢模式不需要考慮線程安全問題,調用速度和訪問速度優於懶漢模式,但是由於它不管是否被調用都會提前創建類的實例,所以資源利用效率比較低,系統加載時間比較長。
懶漢模式實現了延遲加載,但是需要克服多個線程同時訪問的問題,需要通過雙重檢查鎖定機制進行控制,導致系統性能受到一定影響。
三、下面兩個方法實現懶漢模式的線程安全。
為什么會線程不安全?
假設有兩個線程 A B,其中 A 執行到檢查方法,即 if(instance == null) 前,實例都沒有被創建,那么 A 會得到 ture 的結果,但是此時調度算法選擇 B 線程運行,那么當 B 執行 到 if(instance == null) 時得到的也是 true,那這就很尷尬了,兩個線程都會執行 instance = new Singleton2(); 從而創建了兩個實例。
1.雙重檢查鎖定機制。
為了避免以上這種尷尬的情況,需要將這兩行代碼加上同步鎖。但這還不夠完美,每次調用函數得到實例都要試圖加上一個同步鎖(類鎖),而加鎖是一個非常耗時的操作,沒有必要的情況下應該盡量避免。基於這種想法,我們可以在加鎖前再次判斷實例是否為空。這就是雙重檢查鎖定機制。
還有一點,定義 instance 變量時需要使用 volatile 進行修飾,因為需要保證 instance 變量發生修改后可以及時將結果刷新到主內存中,對其他線程可見。
public class Singleton3 { // 私有靜態成員變量 private static volatile Singleton3 instance = null; // 私有構造函數 private Singleton3() { } // 共有靜態工廠方法 public static Singleton3 getInstance() { // 判斷 instance 是否為空,為空->加鎖,創建實例(為了進程安全,再次判斷),不為空->返回實例 if(instance == null) { synchronized (Singleton3.class) { if(instance == null) instance = new Singleton3(); } } return instance; } }
2. 使用靜態內部類創建實例 。(JAVA 語言中最好的實現方法)
public class Singleton4 { // 私有構造函數 private Singleton4() { } // 靜態內部類 private static class HolderClass{ private static final Singleton4 instance = new Singleton4(); } // 靜態公有工廠方法,返回內部類中創建的實例 public static Singleton4 getInstance() { return HolderClass.instance; } }
加載 Singleton4 類的過程中,會為類變量在方法區中分配內存空間並初始化,但是,並不會加載靜態內部類 HolderClass。
當調用 Singleton4.getInstance() ,執行 return HolderClass.instance 語句時,HolderClass 類才會被加載,instance 對象才會被初始化。
上面的解釋說明使用靜態內部類創建實例是 懶加載 的; HolderClass 只會加載一次,保證了 instance 是單例的;類加載過程是線程安全的,保證 instance 初始化的過程是線程安全的。