單例模式屬於創建型模式,保證在程序運行期間一個類只有一個實例,並提供一個全局訪問點
推薦訪問我的個人網站,排版更好看呦:
https://chenmingyu.top/design-singleton/
什么是單例模式
單例模式目的是保證在程序運行期間一個類只有一個實例,並提供一個全局訪問點,無論什么情況下,只會生成一個實例,免去繁瑣的創建銷毀對象的過程。
如何設計單例
如何設計單例模式其實很簡單,只需要考慮一個問題,實例是否可以保證是全局唯一,只要滿足這個條件,這個單例設計的肯定就合格了。
關於實例是否可以保證是全局唯一的延伸出的問題:
- 是否線程安全,不安全肯定就不能保證全局只有一個實例
- 是否支持序列化,支持序列化的類,被反序列化之后肯定就不是全局唯一了
- 是否支持反射,支持反射肯定也不是全局唯一的
- 是否可以被克隆,這個也不能保證全局唯一
所以設計一個安全的單例需要考慮的問題還是很多的。
針對上述問題常見的解決辦法:
- 保證線程安全,使用volatile+synchronized實現
- 防止序列化攻擊,重寫readResolve方法
- 防止反射,常用的方案是在單例類里增加一個boolean類型的flag標識,在實例化的時候先判斷flag標識
- 防止克隆,重寫clone()方法
實現一個最簡單的單例就需要考慮到以上的所有問題,這個時候什么有用的方法還沒寫那,代碼就已經很多了,那有沒有簡單的辦法既滿足上述條件,代碼又簡潔那,那肯定有,使用枚舉實現單例。
常見的單例模式設計方案
常見的單例模式設計方案大概有五種,懶漢模式,餓漢模式,雙重檢查方式實現,靜態內部類實現,枚舉實現。
簡單的分個類:
- 是否支持延遲加載,分為懶漢模式和餓漢模式
- 線程安全設計了雙重檢查模式實現,靜態內部類實現方式
- 不支持序列化,反射,克隆,枚舉實現方式
其中懶漢模式,餓漢模式,雙重檢查方式實現,靜態內部類的實現方式都可以概括為以下兩步:
- 構造函數私有化,保證在外部無法new對象
- 提供一個static方法獲取當前實例(不同方案,實現不同)
當然枚舉的實現方式最簡單,也最安全的,所以推薦使用枚舉實現,其次推薦使用靜態內部類方式實現。
餓漢模式
不是延遲加載,加載類的時候直接初始化
/**
* @auther: chenmingyu
* @date: 2019/2/12 16:26
* @description:
*/
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return singleton;
}
}
優點:線程安全,代碼簡單。
缺點:不是延遲加載,如果你用不到這個類,它也會實例化,還有一個問題就是如果這個實例依賴外部一些配置文件,參數什么的,在實例化之前就要獲取到,否則就實例化異常
懶漢模式
延遲加載,首次需要使用的時候在實例化,需要考慮線程安全
線程不安全的實現方式
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){
if(null == singleton){
singleton = new Singleton();
}
return singleton;
}
}
雙重檢查(DCL:Double Check Lock)
線程安全的實現方式:
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){
if(null == singleton){
synchronized (Singleton.class){
if(null == singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}
面試官:為什么使用volatile修飾singleton變量?
- 說的volatile,首先肯定回答volatile的可見性
- 防止重排序優化,如果不用volatile修飾,多線程的情況下,可能會出現線程A進入synchronized代碼塊,執行new Singleton();,首先給singleton分配內存,但是還沒有初始化變量,這時候線程B進入getInstance方法,進行第一個判斷,此時singleton已經不為空,直接返回singleton,然后肯定報錯。使用volatile修飾之后禁止jvm重排序優化,所以就不會出現上面的問題
靜態內部類實現
使用靜態內部類實現也是延遲加載,利用靜態內部類去實現線程安全,只有在第一次調用getInstance方法的時候才會去加載SingletonHolder,初始化SINGLETON
public class Singleton {
private Singleton() {
}
public static Singleton getInstance(){
return SingletonHolder.SINGLETON;
}
private static class SingletonHolder{
private static final Singleton SINGLETON = new Singleton();
}
}
枚舉實現
枚舉實現代碼更簡潔,線程安全,並且保證枚舉不會被反序列化,反射和克隆
/**
* @auther: chenmingyu
* @date: 2019/2/12 16:30
* @description:
*/
public enum Singleton {
SINGLETON;
/**
* 提供的方法
*/
public void method(){
System.out.println("枚舉實現");
}
}
所以推薦使用枚舉方式,調用Singleton.SINGLETON.method();