單例模式

單例模式

單例模式是為了確保某個實例只會被初始化一次,確保我們每次使用到的對象都是同一個,這樣能夠大大節(jié)約應(yīng)用占用的內(nèi)存。

單例寫法

單例模式的寫法很多,還分懶漢式跟餓漢式,區(qū)別就在于一個是需要時才實例化一個是類被加載時就完成實例化。我們來看一下合理的且常見的幾種懶漢單例寫法

DCL(Double Check Lock,雙重檢查鎖)

public class Singleton {

    private static Singleton sSingleton;

    private Singleton(){}

    public static Singleton getInstance(){
        if(sSingleton == null){
            synchronized (Singleton.class){
                if(sSingleton == null){
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }

}

DCL雙重檢查鎖。內(nèi)層檢查是為了支持線程并發(fā),保證在并發(fā)情況下單例的保持,外層檢查是為了在處理同步問題后進一步提升代碼效率。這種寫法問題在于代碼重拍導(dǎo)致判斷失誤。

代碼重拍問題

首先,new Singleton()不是一個原子操作,它可以分為:分配內(nèi)存空間、初始化對象、將對象指向分配的內(nèi)存空間。而且只有在將對象指向分配的內(nèi)存控件后sSingleton != null才成立。在編譯器編譯時為了優(yōu)化單線程中的執(zhí)行性能是可以將2,3兩步進行重排。重拍后就變成:分配內(nèi)存空間、將對象指向分配的內(nèi)存空間、初始化對象。這樣的話在多線程情況下是有可能產(chǎn)生一種情況:某個線程獲取到的sSingleton對象是分配了地址,但是卻沒有完成實例化的。所以,我們來看下一種方式。

DCL + volatile

public class Singleton {

    private volatile static Singleton sSingleton;

    private Singleton(){}

    public static Singleton getInstance(){
        if(sSingleton == null){
            synchronized (Singleton.class){
                if(sSingleton == null){
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }

}

為單例成員變量增加了 volatile關(guān)鍵字。volatile關(guān)鍵字的作用主要是:保持內(nèi)存可見性與防止指令重排序。很顯然我們是利用它避免new Singleton()操作的指令重排。

volatile原理淺析

保存內(nèi)存可見性
Java是通過幾種原子操作完成工作內(nèi)存和主內(nèi)存的交互:
lock:作用于主內(nèi)存,把變量標(biāo)識為線程獨占狀態(tài)。
unlock:作用于主內(nèi)存,解除獨占狀態(tài)。
read:作用主內(nèi)存,把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存。
load:作用于工作內(nèi)存,把read操作傳過來的變量值放入工作內(nèi)存的變量副本中。
use:作用工作內(nèi)存,把工作內(nèi)存當(dāng)中的一個變量值傳給執(zhí)行引擎。
assign:作用工作內(nèi)存,把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量。
store:作用于工作內(nèi)存的變量,把工作內(nèi)存的一個變量的值傳送到主內(nèi)存中。
write:作用于主內(nèi)存的變量,把store操作傳來的變量的值放入主內(nèi)存的變量中。

被volatile修飾的變量
read、load、use動作必須連續(xù)出現(xiàn)
assign、store、write動作必須連續(xù)出現(xiàn)
因此就能夠保證變量的值對于不同線程是實時可見的

防止進行指令重排
編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。插入內(nèi)存屏障相當(dāng)于告訴CPU跟編譯器,先于這個命令的比較先執(zhí)行,而后于這個命令的必須后執(zhí)行。

volatile沒有原子性
volatile似乎是有時候可以代替簡單的鎖,volatile僅僅用來保證該變量對所有線程的可見性,但不保證原子性。不要將volatile用在getAndOperate場合(這種場合不是原子操作,需要再加鎖),僅僅set或者get等操作的場景是適合volatile的,因為非原子操作情況下,代碼可能被拆分為多個原子操作,多步操作的情況下就無法保證變量的線程安全,在中間過程中可能在其他線程中被修改。
(參考:https://www.cnblogs.com/rainwang/p/4398488.html

靜態(tài)內(nèi)部類

public class Singleton {
    
    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.mSingleton;
    }

    private static class SingletonHolder{
        private static Singleton mSingleton = new Singleton();
    }

}

這種方式主要利用的ClassLoader加載類只會加載一次的特性以及通過內(nèi)部靜態(tài)類去持有單例的對象,保證了單例只會是唯一的,只會初始化一次,同時在調(diào)用getInstance方法時才會去加載SingletonHolder類以及實例化單例對象。

Android中用到的單例模式

不管是Android源碼,還是各種依賴庫框架都經(jīng)常會使用到單例模式。

EventBus#getDafult()

  private static volatile EventBus defaultInstance;
  public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

InputMethodManager#getInstance()

    static InputMethodManager sInstance;
    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                try {
                    sInstance = new InputMethodManager(Looper.getMainLooper());
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
            }
            return sInstance;
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容