Android 面試之單例模式

MissMyDearBear發表於2018-01-02

單例模式是java設計模式之一。這種模式涉及到一個單一的類,該類負責建立自己的物件,並確保是單一的物件。這個類提供直接訪問其單一物件的方式,且不需要例項化該類的物件。

特點

  1. 單例類只能有一個例項
  2. 單例類必須自己建立自己唯一的例項。建構函式是私有的,外部是無法例項化該類。
  3. 單例類必須給所有其他物件提供這一例項。

優缺點

  1. 優點
  • 減少程式內部例項數目,節省系統資源
  • 全域性使用的例項可以避免其頻繁的建立與銷燬
  • 避免對資源的多重佔用
  1. 缺點
  • 沒有介面,不能繼承
  • 與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化

實現

  1. 按照概念,我們實現一下單例模式,如下:
public class SingleInstanceClass {
    //建立自己的物件
    private static SingleInstanceClass instanceClass=new SingleInstanceClass();
    //私有構造方法
    private  SingleInstanceClass(){

    }
    //對外提供獲取該類物件的方法
    public static SingleInstanceClass getInstanceClass(){
        return instanceClass;
    }
}

複製程式碼

這就完了嗎?

Android 面試之單例模式

那必須接著折騰,上面程式碼雖然完全實現了單例。但是會發現,如果程式中沒有使用到這個物件,他依然會在編譯時建立對應的例項,這樣就浪費了資源。於是便有了懶載入的建立單例方式。

  1. 懶載入模式 按照上面的分析,為了不浪費資源我們需要在使用給物件的時候再去建立它的例項物件,也就是懶載入模式。看下面的程式碼:
public class SingleInstanceClass {
    private static SingleInstanceClass instanceClass;
    //私有構造方法
    private  SingleInstanceClass(){

    }
    //對外提供獲取該類物件的方法,且呼叫此方法時,建立例項
    public static SingleInstanceClass getInstanceClass(){
        //保證唯一性
        if(instanceClass==null){
            instanceClass=new SingleInstanceClass();
        }
        return instanceClass;
    }
}
複製程式碼

與放法一不同之處在於,只有當我們使用SingleInstanceClass.getInstanceClass()方法時才會例項化該物件。但是,如果是在多執行緒中呢?這種方式又有弊端了,多執行緒有可能依然會多次例項化這個物件。那為解決這個問題我們來看第三種方式。

  1. 懶載入,執行緒安全方式
public class SingleInstanceClass {
    private static SingleInstanceClass instanceClass;
    //私有構造方法
    private  SingleInstanceClass(){

    }
    //對外提供獲取該類物件的方法,且呼叫此方法時,建立例項,加入執行緒鎖
    public static synchronized SingleInstanceClass getInstanceClass(){
        //保證唯一性
        if(instanceClass==null){
            instanceClass=new SingleInstanceClass();
        }
        return instanceClass;
    }
}
複製程式碼

synchronized 同步鎖,多執行緒併發時,同一時間只會執行一個執行緒。

這種方法因為加了鎖,會導致執行效率變低,於是乎為了提高執行效率,且又能保證執行緒安全。又演變出第四中方式。

  1. 雙檢鎖/雙重校驗鎖(DCL,即double-checked locking)
public class SingleInstanceClass {
    private volatile static SingleInstanceClass instanceClass;
    //私有構造方法
    private  SingleInstanceClass(){

    }
    //對外提供獲取該類物件的方法,且呼叫此方法時,建立例項
    public static  SingleInstanceClass getInstanceClass(){
        //保證唯一性
        if(instanceClass==null){
          synchronized (SingleInstanceClass.class){
              if(instanceClass==null){
                  instanceClass=new SingleInstanceClass();
              }
          }
        }
        return instanceClass;
    }
}
複製程式碼

一旦一個共享變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:1. 保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。2. 禁止進行指令重排序

除此之外還有其它的方式如下

  1. 登記式/靜態內部類 這種方法,要比上面的方法實現上面簡單許多,且效果是一樣的。
public class SingleInstanceClass {
    //靜態內部類中建立外部類的例項
    private static class SingleHolder {
        private static SingleInstanceClass INSTANCE = new SingleInstanceClass();
    }

    //私有構造方法
    private SingleInstanceClass() {

    }

    public static SingleInstanceClass getInstance() {
        return SingleHolder.INSTANCE;
    }

}
複製程式碼

經驗之談:一般情況下,不建議使用第 1 種和第 2 種方式,建議使用第 3 種方式。只有在要明確實現 懶載入效果時,才會使用第 5 種登記方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。

?我的公眾號歡迎大家關注

Android 面試之單例模式

相關文章