1. 單例模式的核心:兩私一公
1. 本單例類的private的static屬性
private static Object instance,用于接收本單例類的單例實例。
2. 本單例類的private的構造方法
防止別的類通過new 的方式創建。
(但是可以被反射破解)
3. 本單例類的public的static方法getInstance
因為別的類不能拿到對象,所以只能通過類名點方法觸發創建對象。即Public靜態方法。
2. 餓漢模式
(類加載時構造實例,耗時)(線程安全)
為什么叫餓漢:因為是Hungry 類初始化時創建單例,而不是需要時再創建單例。
public class Hungry { private static Hungry instance = new Hungry(); private Hungry() { } public static Hungry getInstance() { return instance; } }
3. 普通懶漢模式
(N個線程同時調用getInstance,會忽略if (instance == null) ,會生成N個Lazy類的實例,即N個單例實例,線程不安全)(單線程場景沒毛病)
為什么叫懶漢:因為是需要時再創建單例,而不是類加載時創建單例。
public class Lazy { private static Lazy instance; private Lazy (){ } public static Lazy getInstance() { if (instance == null) { instance = new Lazy(); } return instance; } }
4. 普通懶漢模式——>Synchronized 懶漢模式
(雖然線程安全,但是在高并發的情況下,所有線程都必須執行synchronized方法,串行性能下降)
public class SyncLazy { private static SyncLazy instance; private SyncLazy() { } public static synchronized SyncLazy getInstance() { if (instance == null) { instance = new SyncLazy(); } return instance; } }
5. 普通懶漢模式——>Synchronized 懶漢模式——> Volatile + DCL( Double Check Lock)
此模式在Synchronized 懶漢模式的基礎上:
1. synchronized方法改為方法內部的synchronized塊 + synchronized塊外部加了一個判空的邏輯
2. singleton屬性被volatile修飾
改動1的原因:
避免了所有線程都執行synchronized方法,提高效率。而是改為只有第一次創建單例時且有并發競爭時的兩個線程才會執行synchronized方法
改動2的原因:
而Volatile的作用有兩個:
1. 可見性:線程A實例化屬性之后,Volatile刷入主存
2. 防止指令重排:
singleton = new Singleton();的正常順序是:
1. 聲明屬性時,為對象分配堆空間,初始時為null(3行)
2. 堆中初始化對象(11行的后半句),為Store指令。
3. 棧中的指針指向堆的地址空間(11行的后=前半句),為Load指令。
但是沒有Volatile修飾singleton 屬性時,
2和3會發生指令重排,如果3排到了線程A退出monitorexit+B線程monitorenter+B線程判空的之后,則B線程判空結果為true,則B線程又會創建一個單例實例,所以此時堆中就會有兩個實例。
所以,為了防止指令Store-Load重排,使用Volatile修飾singleton 屬性
public class Singleton { private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
6. 普通懶漢模式——>靜態內部類模式
靜態內部類的靜態屬性的初始化(即創建目標單例實例)時機:訪問靜態內部類的靜態屬性時(即調用Singleton類 點 getInstance()方法時)
靜態內部類天然防止多線程問題。
為什么是懶漢模式而不是餓漢模式:
因為餓漢模式實在類加載時就已經創建完單例了,而懶漢模式則是需要時(即訪問靜態內部類的靜態屬性時)再創建。
public class Singleton { private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.Instance; } private static class SingletonHolder { private static final Singleton Instance = new Singleton(); } }
7. FAQ
7.4 如何防止反射攻擊DCL:
以上的單例模式實現雖然很好,但是如果使用反射為私有構造函數setAccessible,然后通過私有構造函數.newInstance()的話,就會把單例模式破解。因為是觸發了第二次new對象。
我們可以稍作修改防止這種反射攻擊:
把構造函數改成:
private Singleton(){ if (instance!= null) { throw new RunTimeException(“單例模式不允許創建多個實例”) } } // instance代表的是單例類里面的存放單例的那個屬性。

浙公網安備 33010602011771號