1. 程式人生 > >單例模式及其在Android中的應用

單例模式及其在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;
        }
    }


}

從程式碼中可以看到,用到了第二種也就是懶漢模式。