一、前言
單例模式已經是一個非常常見的設計模式,在我們日常開發中也是非常容易遇到,但是你是不是真的掌握了呢?我們一起來探討一下。
二、單例模式實現方式
作爲一名程序員,我們經常在面試的時候被面試官問到,你會寫幾種單例模式?請你講下餓漢模式和飽漢模式的區別?飢漢模式和懶漢模式的區別?雙重校驗鎖的兩個判空作用分別是什麼?哪些實現是線程安全?諸如此類的問題,如果你對單例模式掌握得不夠深,你一定會手忙腳亂,但是如果你掌握了,那麼這一切對你來說都是小菜一碟。
1.懶漢(飽漢)模式和餓漢(飢漢)模式
區別:
- 懶漢(飽漢)模式:實例在開始時爲空,等到第一次加載時纔會實例化。
- 餓漢(飢漢)模式:在類加載前就已經實例化。
懶漢(飽漢)模式
/**
* 懶漢(飽漢)模式
* 懶加載:是
* 線程安全:是
* 描述:既實現了懶加載,也是線程安全,但是效率低,99%的情況下都不需要同步
* 優點:第一次調用才初始化,避免了內存浪費
* 缺點:必須加鎖synchronized才能保證同步,但加鎖會影響效率
*/
class LazyDogSingleInstance {
private static LazyDogSingleInstance sInstance;
public static synchronized LazyDogSingleInstance getInstance() {
if (sInstance == null) {
sInstance = new LazyDogSingleInstance();
}
return sInstance;
}
}
餓漢(飢漢)模式
/**
* 餓漢(飢漢)模式
* 懶加載:否
* 線程安全:是
* 描述:比較常用,但是容易產生垃圾對象
* 優點:沒有加鎖,執行效率會提高
* 缺點:類加載時就初始化,浪費內存
*
*/
class HungryDogSingleInstance {
private static HungryDogSingleInstance sInstance = new HungryDogSingleInstance();
private HungryDogSingleInstance() {}
private static HungryDogSingleInstance getInstance() {
return sInstance;
}
}
2.實現雙重校驗鎖模式
雙重判空的意義分別是什麼:第一個判空是判斷當前對象是否爲空,判斷是否需要實例化,第二個判空是因爲加了鎖,有可能在等待鎖的時候這個對象已經被實例化,所以獲取鎖的時候需要重新判斷一次這個對象是否爲空。
/**
* 雙重校驗鎖模式
* 懶加載:是
* 線程安全:是
* 描述:使用了雙重校驗鎖,安全且在多線程下能保持高性能
*
*/
class DoubleCheckSingleInstance {
private static DoubleCheckSingleInstance sInstance;
private DoubleCheckSingleInstance() {
}
private static DoubleCheckSingleInstance getInstance() {
if (sInstance == null) {
synchronized (DoubleCheckSingleInstance.class) {
if (sInstance == null) {
sInstance = new DoubleCheckSingleInstance();
}
}
}
return sInstance;
}
}
3.實現靜態內部類
/**
* 靜態內部類模式
* 懶加載:是
* 線程安全:是
* 描述:利用了類加載機制保證初始化INSTANCE時只有一個線程,在初始化StaticInnerSingleInstance沒有實例化INSTANCE,而是在調用getInstance時才實例化
*
*/
class StaticInnerSingleInstance {
private static class Holder {
private static final StaticInnerSingleInstance INSTANCE = new StaticInnerSingleInstance();
}
private StaticInnerSingleInstance() {}
public static final StaticInnerSingleInstance getInstance() {
return Holder.INSTANCE;
}
}
三、單例模式優缺點
- 全局只有一個對象,對單例類的所有實例化得到都是同一個對象,這樣就可以防止其它對象對自己的實例化,確保所有的對象都訪問一個實例。
- 單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。
- 提供了對唯一實例的受控訪問。
- 由於在系統內存中只存在一個對象,因此可以節約系統資源,當需要頻繁創建和銷燬對象時,單例模式無疑可以提高系統的性能。
- 允許可變數目的實例。
- 避免對共享資源的多重佔用。
缺點:
- 不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。
- 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
- 單例類的職責過重,在一定程度上違背了“單一職責原則”。
- 濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。
四、Android中單例模式應用場景
1.AccessibilityManager
public final class AccessibilityManager {
private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
public static AccessibilityManager getInstance(Context context) {
return sInstance;
}
}
2.LayoutInflater
public class ContextThemeWrapper extends ContextWrapper {
private LayoutInflater mInflater;
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
}