單例模式及其在Android中的應用
單例模式算是設計模式中最簡單的模式了,主要是為了保證類只有一個例項,比如保持一個請求佇列等。類圖也很簡單,如下所示:
可以看到,類中有一個型別是本類的私有變數,加上私有的構造方法和公共的getInstance()方法。這樣就保證了其它類不能隨意的例項化它,必須通過公共的方法才能得到它的例項。
寫法有很多種,這裡只列出最常用的4種:餓漢、懶漢、DCL(Double Check Lock 雙檢查鎖)和靜態內部類。
餓漢
/**
* 餓漢單例模式
*/
public class Singleton {
// 直接例項化自己並賦值給mInstance
private static Singleton mInstance = new Singleton();
// 私有構造方法,保證只有自己才能例項化
private Singleton() {
}
// 外部只有通過此方法得到例項
public static Singleton getInstance() {
return mInstance;
}
}
餓漢模式在定義變數是直接就例項化類了,這樣做肯定能保證其它類得到是唯一的。但是這樣做有一個壞處,假設這個類從來沒被用到,那就浪費了。
懶漢
/**
* 懶漢單例模式
*/
public class Singleton {
// 定義私有的變數,並沒有賦值
private static Singleton mInstance;
// 私有構造方法
private Singleton() {
}
// 通過此方法,得到本類的例項化
// 注意synchronized關鍵字加鎖,適用於多執行緒
public static synchronized Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
}
通過程式碼,可以看到跟餓漢的最大區別是:餓漢在定義變數時直接賦值,而懶漢比較懶,用到了才臨時抱佛腳,去例項化類。
同時注意在getInstance()方法加上了synchronized關鍵字,如果有兩個執行緒同時訪問此方法,而沒有加鎖的話,就會新建兩個例項,那這單例模式就形同虛設了。所以就需要加鎖保護,當一個執行緒訪問時,其它執行緒就阻塞,當訪問結束,才釋放鎖,才其它執行緒訪問。
仔細想想,加上了synchronized的確是保證了執行緒安全,但是如果mInstance不為空了,我們只需要返回就行。由於加了鎖,所有訪問都必須等待前一個訪問釋放,是不是有點不合理? 看接下來的DCL模式可以避免這個問題。
DCL (Double Check Lock)
public class Singleton {
// 私有變數,注意volatile關鍵字
private static volatile Singleton mInstance;
// 私有構造方法
private Singleton() {
}
// 公共訪問方法,兩次判斷是否為空
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
我們重點看getInstance()方法,先判斷是否為空,如果不為空,直接返回,這就解決了懶漢模式中提到的不必要的加鎖保護。
在往下看synchronized同步程式碼塊,如果為空時才執行此程式碼塊。在這裡再次判斷mInstanc是否為空,想一想,如果同時有兩個執行緒走到這裡,如果沒有再次判斷是否為空的話,也會例項化兩次。雙檢查鎖的兩次判空就保證了類只例項化一次
最後,注意一下mInstance變數前多了一個volatile關鍵字,要想知道此關鍵字的用意,首先得了解java的記憶體模型。直接看下圖:
簡單描述一下,改變一個變數的值,也就是主記憶體中的值,必須通過3個步驟:assign+store+write操作,讀也是需要3個步驟:read+load+use。加上volatile關鍵字就可以保證按照順序進行讀寫,並且每次讀取都是從主記憶體中獲取真實值。
靜態內部類
DCL在高併發情況下會出現失效問題,如果有高併發的情況,不建議使用,推薦使用靜態內部類方式,程式碼如下:
/**
* 靜態內部類單例模式
*/
public class Singleton {
// 私有構造方法
private Singleton() {
}
// 單一全域性訪問點
public static Singleton getInstance() {
return SingletonHolder.mInstance;
}
// 靜態內部類,第一次載入Singleton類時不會初始化mInstance,
// 當呼叫getInstance()時才會初始化
private static class SingletonHolder {
private static Singleton mInstance = new Singleton();
}
}
可以看到,當呼叫getInstance()方法時,jvm才會載入SingletonHolder內部類,能確保執行緒安全,還能夠保證物件的唯一性,沒有上述3種實現方式的缺點。
Android中的單例模式
Android中用到單例模式的地方還是蠻多的,舉兩個例子。
Android-Universal-Image-Loader
我們通過網路載入圖片的時候一般都會接觸此框架,很簡單的就可以使用,如下程式碼:
ImageLoader.getInstance().displayImage(url, imageView);
看看原始碼,如下:
public class ImageLoader {
// 省略若干程式碼
private volatile static ImageLoader instance;
/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
protected ImageLoader() {
}
// 省略若干程式碼
可以看到,原始碼中使用了DCL的方式去實現的單例模式。
DisplayManagerGlobal
該類主要負責管理顯示管理器(Display Manager)與顯示管理服務(Display Manager Service)之間的通訊。其中也用到了單例模式,程式碼如下所示:
public final class DisplayManagerGlobal {
// 省略若干程式碼
private static DisplayManagerGlobal sInstance;
public static DisplayManagerGlobal getInstance() {
synchronized (DisplayManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
if (b != null) {
sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
}
}
return sInstance;
}
}
}
從程式碼中可以看到,用到了第二種也就是懶漢模式。