創建型-單例模式
了解單例
單例模式是一種創建型設計模式,它提供了一種創建對象的最佳方式;它必須保證:
- 單例類只能有一個實例化對象;
- 單例類必須創建自己的唯一實例;
- 單例類必須給其他對象提供實例;
另外:
- 它的目的是:確保一個類只用一個實例,并提供一種全局訪問入口來訪問該實例
- 設計思想:在獲取實例的時候判斷實例是否存在,如果存在,則直接返回,如果不存在則創建實例;
- 關鍵代碼:構造方法私有化;
角色
- 單例類:包含單例實例的類
- 靜態成員變量:用于存儲單例的靜態成員變量,final修飾防止被繼承
- 獲取實例方法:靜態方法,用于獲取單例實例
- 私有構造方法:防止外部直接實例化類
- 線程安全處理:確保多線程環境下單例創建的安全性
實現方式
餓漢式單例
特點:類一加載就實例化單例對象
public class Mgr01 {//靜態成員變量存儲單例,final修飾防止被繼承private final static Mgr01 INSTANCE = new Mgr01();//構造方法私有化,防止外部直接實例化private Mgr01() {}//靜態方法,用于獲取單例public static Mgr01 getInstance(){return INSTANCE;}
}
另一種寫法,在靜態代碼塊中實例化對象
public class Mgr02 {private final static Mgr02 INSTANCE ;static {INSTANCE = new Mgr02();}public static Mgr02 getMgr02() {return INSTANCE;}
}
懶漢式單例
特點: 使用單例時才實例化對象
線程不安全寫法:
public class Mgr03 {private static Mgr03 INSTANCE;private Mgr03() {}public static Mgr03 getInstance(){//模擬執行其他操作所用的時間if( INSTANCE == null){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}INSTANCE = new Mgr03();}return INSTANCE;}
}
synchronized鎖獲取實例靜態方法,保證線程安全:
public class Mgr04 {private static Mgr04 INSTANCE;private Mgr04() {}public static synchronized Mgr04 getInstance(){//模擬執行其他操作所用的時間if( INSTANCE == null){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}INSTANCE = new Mgr04();}return INSTANCE;}
}
雙重檢驗鎖(DCL)單例:
- 第一層null值檢測是為了在已經存在單例的情況下不需要等鎖提高效率,第二次null判斷是為了保證單例。
- volatile關鍵字的作用:如果不使用volatile關鍵字那么,創建單例過程可能被拆分為以下幾步,①為單例對象分配內存空間;②初始化單例對象;③將INSTANCE變量指向分配的內存空間。在沒有volatile 關鍵字的情況下,步驟②和③可能會被重排序。這就可能導致其他線程在執行getInstance() 方法時,看到的 INSTANCE 變量已經被賦值,但單例對象并沒有被完成初始化。
public class Mgr06 {private static volatile Mgr06 INSTANCE;//volatile 是為了防止JVM中語句重排private Mgr06() {}public static Mgr06 getInstance(){//這個判斷可以屏蔽很多操作,很多線程到這,如果已INSTANCE已經存在,可以減少下面代碼的執行,提升效率if( INSTANCE == null){synchronized (Mgr06.class){if(INSTANCE == null){try {Thread.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}INSTANCE = new Mgr06();}}}return INSTANCE;}
}
靜態內部類單例
這種方法是通過JVM保證單例,JVM在加載外部類時,只加載一次,且內部類在使用時才會加載,也就是第一次調用獲取實例的方法時候才會調用。
其中,內部類私有化,內部類中的靜態變量也私有化;
public class Mgr07 {private static class MGR_07{private final static Mgr07 INSTANCE = new Mgr07();}public static Mgr07 getInstance(){return MGR_07.INSTANCE;}
}
枚舉單例
枚舉單例不但可以保證單例,還可以防止反序列化,因為枚舉沒有構造方法。
public enum Mgr08 {INSTANCE
}