一、是什么
確保一個類只有一個實例,並提供一個全局訪問點
一般分類兩大類: 餓漢模式、懶漢模式
使用: 以前在線白鷺H5游戲時,因為有很多的場景類, 而每個場景類不需要創建很多遍, 所以使用單例模式
二、示例
1. 餓漢模式
/** * 餓漢模式, 線程安全, 但默認就創建實例, 占用空間 */ public class Singleton1 { private static final Singleton1 instance = new Singleton1(); private Singleton1() { } public static Singleton1 getIntance() { return instance; } }
用空間換時間,默認就創建實例,所以沒有線程安全問題
2. 懶漢模式
/** * 懶漢模式, 線程不安全 */ public class Singleton2 { private static Singleton2 instance = null; private Singleton2() { } public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
現在不安全在於,多個線程訪問getInstance()時,當一個線程已經初始化了,而另外一個線程並沒有感知,又重新創建了實例,這時候就不是單例
2.1 雙檢鎖 Double-check
/** * 懶漢模式--雙檢索 */ public class SingletonDoubleCheck { private static SingletonDoubleCheck instance = null; private SingletonDoubleCheck() { } public static SingletonDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDoubleCheck.class) { if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } }
為了在懶漢模式的基礎上,保證線程安全, 出現了雙檢鎖的設計,但是有出現了另一個問題。
在new SingletonDoubleCheck()時,是非原子性的,實際分為三步
- new 分配內存空間
- 初始化對象
- 將對象指向剛分配的內存空間
但JVM編譯器,為了性能考慮,可能重新排序2,3兩個, 變為:
- new 分配內存空間
- 將對象指向剛分配的內存空間
- 初始化對象
舉例說明:
線程1檢查到instance為空,獲取鎖,再次檢查instance為空,為instance分配內存空間,指向內存空間,這時線程2檢查到instance不為空,直接返回instance,但此時對象還沒有初始化完成
2.2 雙檢鎖 線程安全
/** * 使用volatile關鍵字的雙檢鎖 */ public class SingletonDoubleCheck2 { /** * volatile關鍵字保證我在鎖instance時, 禁止JVM重排序 */ private volatile static SingletonDoubleCheck2 instance = null; private SingletonDoubleCheck2() { } public static SingletonDoubleCheck2 getInstance() { if (instance == null) { // 再次減少鎖的范圍, 只鎖instance變量 synchronized (instance) { if (instance == null) { instance = new SingletonDoubleCheck2(); } } } return instance; } }
使用volatile關鍵字來禁止JVM重排序
3.3 內部類實現
/** * 靜態內部類實現 -- 延遲加載 * * 天生線程安全 */ public class Singleton3 { /** * 私有化構造 */ private Singleton3() { System.out.println("初始化"); } /** * 靜態內部類 */ private static class InnerObject{ private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance() { return InnerObject.instance; } public static void main(String[] args) { getInstance(); } }
3.4 靜態代碼實現
/** * 懶漢模式 -- 靜態代碼塊實現 */ public class Singleton4 { private static Singleton4 instance = null; static { instance = new Singleton4(); } public static Singleton4 getInstance() { return instance; } }
外部類加載時並不需要立即加載內部類,所以可以起到延時加載的目錄,
三、總結
單例模式是一個創建型的設計模式,能夠幫助開發者創建一個唯一的實例
使用的還是挺頻繁的