【Design Pattern】Singleton 單例模式

一個類別只有一個實體化的物件(Instance),取得物件有統一的方法。
singleton 基本特點:

  • sealed class
  • private constructor without parameter
  • static variable hold instance
  • public static method or property get instance variable

sealed 這個關鍵字表示這個 class 不能被繼承。
private constructor 表示這個 class 不會被其他地方建立 instance。
其實不寫以上兩點程式也可以正常運作,但有寫的話可以讓閱讀程式碼的人一眼就知道這個 class 是 singleton,尤其多人開發時,保持良好的寫作習慣才能避免挖坑給別人跳。

singleton 有很多種寫法

第一種:貪婪單例模式(Greed Singleton or Eager Singleton) – thread safe

public sealed class GreedySingleton
{
    private static readonly GreedySingleton _instance = new GreedySingleton();//程式一啟動就建立instance
    //Explicit static consturctor to tell C# compiler 
    //not to mark type as beforefieldinit
    static GreedySingleton() { }
    private GreedySingleton() { }

    public static GreedySingleton GetInstance()
    {
        return _instance;
    }
}

貪婪單例的缺點是程式一啟動就建立 instance,如果程式運行中不需要使用這個 instance,那就變成無謂的浪費資源了。如果想要使用的時候再建立 instance 會改成以下寫法。

第二種:惰性初始化 – non thread safe

public sealed class LazySingleton
{
    private static LazySingleton _instance = null;

    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        if (_instance == null) //當null的時候才建立instance
            _instance = new LazySingleton();
        return _instance;
    }
}

單執行緒的時候這樣的寫法沒問題,但多執行緒時可能都剛好遇到 instance = null 的情況,分別都建立 instance,就會取得不同的 instance。
為了避免這種情況,會再加上 lock

第三種:惰性初始化 – thread safe – lock(bad performance)

public sealed class LazySingleton
{
    private static LazySingleton _instance = null;
    private static readonly object _objLock = new object();

    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        lock (_objLock)//確保一次只有一個thread可以進入
        {
            if (_instance == null) 
            {
                _instance = new LazySingleton();
            }
            return _instance;
        }
    }
}

但是這種寫法,只要 GetInstance() 都會因為 lock 而一直在等待導致效能變差,實際上需要 lock 的只有 new LazySingleton() 而已,因此會再改成以下寫法,這種寫法大家稱為 double check locking

第四種:惰性初始化 – thread safe – double check locking

public sealed class LazySingleton
{
    private static LazySingleton _instance = null;
    private static readonly object _objLock = new object();

    private LazySingleton()
    {
    }

    public static LazySingleton GetInstance()
    {
        if (_instance == null)//first check
        {
            lock (_objLock)
            {
                if (_instance == null)//double check
                {
                    _instance = new LazySingleton();
                }
            }
        }
        return _instance;
    }
}

第五種:惰性初始化 – 使用 .Net 4 Lazy<T>

//thread safe
public sealed class LazySingleton
{
    private static readonly Lazy<LazySingleton> _instance = new Lazy<LazySingleton>(() => new LazySingleton());
    private LazySingleton() { }
    public static LazySingleton Instance
    {
        get
        {
            return _instance.Value;
        }
    }
}

第六種:完全惰性初始化(fully lazy instantiation)

public sealed class LazySingleton
{
    public static LazySingleton Instance { get { return LazySingletonNested.instance; } }

    private LazySingleton() { }

    private class LazySingletonNested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static LazySingletonNested()
        {
        }

        internal static readonly LazySingleton instance = new LazySingleton();
    }
}

參考資料:
https://csharpindepth.com/articles/Singleton

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *