最近面了好多家公司,幾乎都會問到有關單例模式的問題,當然基礎的單例模式大家可能都寫得出來,但面試官深入問下去呢?很可能就矇了,所以在這裡就整理幾個單例模式防止自己忘記。
單例模式是做什麼的?
根據維基百科的解釋:
懶漢式(線程不安全):
下面這個單例模式是最基礎的單例模式,只有需要加載這個實例的時候才會進行加載。
package Interview.Singleton.LazyInitialization;
public class SingletonBasic {
private static SingletonBasic instance = null;
private SingletonBasic() {
}
public static SingletonBasic getInstange() {
if(instance == null)
instance = new SingletonBasic();
return instance;
}
}
這種單例模式雖然簡單,但也存在致命的問題,就是在多線程環境時,多個線程調用getInstance()時,並無法保證只有一個實例。在面試的時候問到了單例模式,寫出上面這種模式,面試官肯定會繼續追問下去(當然也可以寫出來拖延時間XD~~~)。
懶漢式(線程安全)
既然最基礎的懶漢式無法滿足要求,那只要將getInstance()設為同步,就可以保證只有一個實例。
package Interview.Singleton.LazyInitialization;
public class SingletonSynchronized {
private static SingletonSynchronized instance = null;
private SingletonSynchronized() {
}
public static synchronized SingletonSynchronized getInstange() {
if(instance == null)
instance = new SingletonSynchronized();
return instance;
}
}
雖然上面這種方法保證了線程安全,但它的效率不高(synchronized效率本來就不怎樣),故我們又引入了下面另外一種方法。
懶漢式(雙重檢驗鎖)
這種方法使用雙重檢驗,第一次檢驗是否實例存在,第二次是在同步中再一次檢查是否有實例存在,第二次檢查是為了防止多線程同時調用生成多個實例。
package Interview.Singleton.LazyInitialization;
public class SingletonDoubleCheckLocking {
private static volatile SingletonDoubleCheckLocking instance = null;
private SingletonDoubleCheckLocking() {
}
public static SingletonDoubleCheckLocking getInstance() {
if(instance == null)
synchronized (SingletonDoubleCheckLocking.class) {
if(instance == null) {
instance = new SingletonDoubleCheckLocking();
}
}
return instance;
}
}
為什麼instance要聲明為volatile(保證instance這個不會指到未分配的內存空間)呢?因為在新增instance這個實例的時候,Java虛擬機會做下面三件事情:
- 為instance分配內存空間
- 調用SingletonDoubleCheckLocking的構造函數(建構子)
- 將instance的指針指向1所分配的空間
但Java虛擬機會進行指令重排序的優化,所以不能保證是1 2 3順序執行,可能是1 3 2或是其他執行順序,而多個線程在運行時可能會在instance正在初始化的時候就搶佔了這個instance,導致instance沒有建構完成就返回這個實例,想當然就會返回一個null,然後就跳出錯誤啦@@
但這樣寫還滿複雜的,有沒有更簡單一點的方法呢?
餓漢式
使用餓漢式就不用關注這麼多情況了,因為單例的實例已經聲明為final以及static,故在加載這個類的時候,實例就會被初始化,所以實例本身就是線程安全的。
package Interview.Singleton.HurryInitialization;
public class SingletonBasic {
private static final SingletonBasic instance = new SingletonBasic();
private SingletonBasic() {
}
public static SingletonBasic getInstance() {
return instance;
}
}
但這種寫法當然還是有缺點的,例如:
- 有時候不需要實例化該實例的時候,仍然會初始化實例,浪費內存空間
- 或是創建實例的時候需要依賴於參數或是配置文件的時候,並沒辦法指定參數給個實例
尚未完成 待續....