單例模式

什麼是單例模式?

保證一個類只有一個實例,並提供一個全局變量來訪問這個實例,這就是單例模式,單例模式屬於對象建立型模式。安全

單例模式的幾個要素

  • 類只能有一個實例
  • 這個實例必須由該類自己建立
  • 該類必須向整個系統提供該實例的訪問權限

單例模式的結構

image.png

單例模式的實現

class Singleton
{
    private static Singleton s_Instance;

    //私有構造方法,防止外部實例化
    private Singleton() { }

    public static Singleton GetInstance()
    {
        if (s_Instance == null)
            s_Instance = new Singleton();

        return s_Instance;
    }
}

測試代碼多線程

static void Main()
{
    var s1 = Singleton.GetInstance();
    var s2 = Singleton.GetInstance();
    if (object.ReferenceEquals(s1, s2))
        Console.WriteLine($"s1 s2兩個實例是相同的");
    Console.ReadKey();
}

結果
image.png併發

餓漢式單例與懶漢式單例

餓漢式單例(最簡單的單例)

image.png

class EagerSingleton
{
    //類加載時執行
    private static EagerSingleton s_Instance = new EagerSingleton();

    private EagerSingleton() { }

    public static EagerSingleton GetInstance()
    {
        return s_Instance;
    }
}

懶漢式單例

//懶漢式單例與餓漢式單例不一樣的在於:懶漢式單例在第一次被引用時將本身實例化(類被加載時不會實例化自身)
class LazySingleton
{
    private static LazySingleton s_Instance;

    //私有構造方法,防止外部實例化
    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        if (s_Instance == null)
            s_Instance = new LazySingleton();

        return s_Instance;
    }
}

懶漢式單例的線程安全問題

在高併發多線程的環境下運行懶漢式單例的代碼,會出如今某一時刻存在多個線程同時訪問GetInstance方法,可能會建立多個實例,違背了單例模式的設計意圖。高併發

修改懶漢式單例代碼使其線程安全性能

class LazySingleton
{
    private static readonly object s_LockObj = new object();

    private static LazySingleton s_Instance;

    //私有構造方法,防止外部實例化
    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        if (s_Instance == null)
        {
            //加鎖使程序在某一時刻只容許一個線程訪問
            lock (s_LockObj)
            {
                if (s_Instance == null)
                    s_Instance = new LazySingleton();
            }
        }

        return s_Instance;
    }
}

爲何要雙重校驗

當實例不存在,而且多個線程同時調用GetInstance方法,此時它們都能經過第一重"s_Instance == null"的判斷,此時只會有一個線程進入lock塊執行建立代碼,若是不進行第二次的"s_Instance == null"判斷,當第一個線程建立完實例離開後,第二個線程進入lock塊,因爲第二個線程並不知道實例已經被建立,將繼續建立新的實例,仍是會產生多個實例對象。測試

餓漢式單例與懶漢式單例比較

餓漢式單例在類被加載時就建立實例對象,優勢在於無需考慮線程安全問題,因爲單例對象一開始就建立好了,因此調用速度優於懶漢式單例。同時,由於類加載時就須要建立單例對象,所以從資源利用角度來講,餓漢式單例不如懶漢式單例,類的加載時間相對懶漢式單例也更慢。spa

懶漢式單例在第一次使用時建立單例對象,實現了延遲加載,無須一直佔用系統資源。可是懶漢式單例必須處理線程安全問題,這將致使系統性能受到必定程度的影響。線程

單例模式的優勢

  • 系統中只存在一個對象,節約系統資源
  • 提供惟一一個實例,能夠很好的控制如何訪問以及何使訪問它

單例模式的缺點

  • 沒有抽象層,單例類擴展困難
  • 單例類將對象的建立和對象自己的功能耦合在一塊兒,職責太重,在必定程度上違背了類的單一職責原則

在如下狀況能夠考慮使用單例模式

  • 某一個對象在系統中只須要一個或者由於實例化消耗太大隻容許建立一個
  • 該實例只能經過一個公共訪問點訪問
相關文章
相關標籤/搜索