設計模式之“物件效能模式”: Singleton 單例模式(筆記)

KeiHong發表於2019-02-22
設計模式之“物件效能模式”: Singleton 單例模式(筆記)

物件建立模式之“單例模式Singleton”


動機不純

  • 在軟體系統中會有一些特殊的類,需要保證在系統中只有一個例項,以保證他們的邏輯正確性、良好的效率
  • 繞過常規的構造器(Client不能直接new),提供一種機制保證一個類只有一個例項(任何地方,不同類之中使用的都是同一個物件指向同一個記憶體)?
  • 責任:類設計者,而非Client使用者。

Singleton模式定義

保證一個類僅有一個例項,並提供一個該例項的全域性訪問點

模式結構類圖

設計模式之“物件效能模式”: Singleton 單例模式(筆記)

要點

  1. Singleton模式中的例項構造器可以設定為protected以允許子類派生(但要保證子類也是singleton)
  2. Single模式一般不要支援拷貝建構函式、Clone介面(工廠模式),因為會導致多個例項物件,與Singleton模式違背
  3. 多執行緒下的Singleton模式的執行緒安全保證(不因為執行緒搶佔問題讓兩個執行緒都同了null判斷,導致進行了兩次new)?— 雙檢查鎖的正確實現(使用)

最佳實踐

單執行緒

public class Singleton {

    private static Singleton instance;

    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null ) {

            instance = new Singleton();
        }

        return instance;
    }
}


//  Singleton s1=Singleton.getInstance();

//  Singleton s2=Singleton.getInstance();

//  Singleton s3=Singleton.getInstance();
複製程式碼

多執行緒1:同步鎖 – 解決執行緒安全;雙重判斷 – 解決執行緒鎖的效能問題

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() { }

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

        return instance;
    }
}

/*
1. 先分配記憶體
2. 執行構造器
3. 將記憶體地址複製給instance
 */

/*
1. 先分配記憶體
2. 將記憶體地址複製給instance
3. 執行構造器
 */
複製程式碼

多執行緒2:靜態變數的方式解決執行緒安全問題

public class Singleton {
    private Singleton() { }

    //lazy load
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
複製程式碼

注意拾遺

  1. 靜態物件(變數)存在哪裡? static block,只有單執行緒能進去—>保證執行緒安全
    java程式在記憶體中的儲存分配情況:
    堆區: 
    1.儲存的全部是物件,每個物件都包含一個與之對應的class的資訊。(class的目的是得到操作指令) 
    2.jvm只有一個堆區(heap)被所有執行緒共享,堆中不存放基本型別和物件引用,只存放物件本身 
    棧區: **
    1.每個執行緒包含一個棧區,棧中只儲存基礎資料型別的物件和自定義物件的引用(不是物件),物件都存放在堆區中 
    2.每個棧中的資料(原始型別和物件引用)都是私有的,其他棧不能訪問。 
    3.棧分為3個部分:基本型別變數區、執行環境上下文、操作指令區(存放操作指令)。 
    方法區: 
    1.又叫靜態區,跟堆一樣,被所有的執行緒共享。方法區包含所有的
    class和static變數**。 
    2.方法區中包含的都是在整個程式中永遠唯一的元素,如class,static變數。
  2. 多執行緒的情況:執行緒不安全—> 搶佔執行緒的情況,但被判斷的變數為null時,可能兩個執行緒都會通過,物件就會被new兩次,破壞了單例模式
  3. 編譯 reorder :使用volatile,保證以下程式碼編譯結果為情況1(java 5.0才加入的volatile關鍵字機制)

Obj o = new Obj();
情況1:
1 先分配記憶體
2 執行構造器
3 將記憶體地址複製給instance
情況2:
1 先分配記憶體
2 將記憶體地址複製給instance
3 執行構造器

  1. lazy load
    懶載入 — 晚載入:變數到了用到的時候才載入(被呼叫時)
    在Client程式中,只要用到某個類,ClassLoader就會把類中的靜態變數都載入到記憶體中,這樣靜態變數是無法晚載入的,但是被載入的類的內部類是不會被立刻載入,直到執行到主類中呼叫到該內部類的程式碼時才會被載入。
    我們可以通過這種機制,實現一些變數的懶載入 — 建立內部類,把變數放到該內部類中

相關文章