1. 程式人生 > 實用技巧 >建立型-單例模式 SingletonPattern

建立型-單例模式 SingletonPattern

單例模式 Singleton

  • 保證一個類只有一個例項的實現方法
  • 給其他類提供一個全域性的訪問點。
  • 由自己建立自己的唯一例項

實現

  • 實現方法分為餓漢式(執行緒安全)、懶漢式(執行緒不安全)、懶漢式(lock+雙重驗證、執行緒安全)、延遲載入(Lazy、執行緒安全)

1.餓漢式

這種方式比較常用,但容易產生垃圾物件.這時候初始化 instance 顯然沒有達到 lazy loading 的效果。
優點:沒有加鎖,執行效率會提高。
缺點:類載入時就初始化,浪費記憶體。

public class EagerSingleton
{
    private EagerSingleton() { }
    private static readonly EagerSingleton Instance = new EagerSingleton();

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

2.最簡單的實現:懶漢式(執行緒不安全)

namespace Singleton
{
    public class Singleton
    {
        private static Singleton _uniqueInstance;

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_uniqueInstance is null)
            {
                _uniqueInstance = new Singleton();
            }

            return _uniqueInstance;
        }
    }
}

定義了一個靜態方法,作為全域性訪問點,在單執行緒下是正常的,在多執行緒同時執行GetInstance,得到的_uniqueInstance都是null,此時就會建立多個 定義了一個靜態方法,作為全域性訪問點,在單執行緒下是正常的,在多執行緒同時執行GetInstance,得到的_uniqueInstance都是null,此時就會建立多個的例項。

多執行緒訪問得到hash code是不一樣的。

static void Main(string[] args)
{

    Task.Run(() =>
    {
        Singleton singleton = Singleton.GetInstance();
        Console.WriteLine(singleton.GetHashCode());
    });

    Task.Run(() =>
    {
        Singleton singleton = Singleton.GetInstance();
        Console.WriteLine(singleton.GetHashCode());
    });

    Console.WriteLine("over!");
}

over還提前輸出。

over!
4032828
6044116
static void Main(string[] args)
{
    Singleton singleton1 = Singleton.GetInstance();
    Console.WriteLine(singleton1.GetHashCode());

    Singleton singleton2 = Singleton.GetInstance();
    Console.WriteLine(singleton2.GetHashCode());

    Console.WriteLine("over!");
}

輸出

58225482
58225482
over!

3.懶漢式(lock+雙重驗證、執行緒安全)

lock關鍵字

MSDN介紹

lock 關鍵字可確保當一個執行緒位於程式碼的臨界區時,另一個執行緒不會進入該臨界區。 如果其他執行緒嘗試進入鎖定的程式碼,則它將一直等待(即被阻止),直到該物件被釋放。
lock 關鍵字在塊的開始處呼叫 Enter,而在塊的結尾處呼叫 Exit。 ThreadInterruptedException 引發,如果 Interrupt 中斷等待輸入 lock 語句的執行緒。
通常,應避免鎖定 public 型別,否則例項將超出程式碼的控制範圍。

常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:
如果例項可以被公共訪問,將出現 lock (this) 問題。
如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題。
由於程序中使用同一字串的任何其他程式碼都將共享同一個鎖,所以出現 lock("myLock") 問題。
最佳做法是定義 private 物件來鎖定, 或 private static 物件變數來保護所有例項所共有的資料。
在 lock 語句的正文不能使用 等待 關鍵字。

最常使用的鎖是如下格式的程式碼段:

private static object objlock = new object();
lock (objlock )
{
    //要執行的程式碼邏輯
}

使用lock關鍵字解決多執行緒問題

public static LockSingleton GetInstance()
{
    // 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖",
    // 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖
    // lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖"
    lock (Locker)
    {
        // 如果類的例項不存在則建立,否則直接返回
        if (_uniqueInstance == null)
        {
            _uniqueInstance = new LockSingleton();
        }
    }
    return _uniqueInstance;
}

在lock之前判斷是否例項

上面的程式碼還可以優化,通過判斷物件是否為null,如果不是null,則直接返回,否則先鎖,然後再生成例項,保證不同執行緒訪問得到的是一個例項

public static LockSingleton GetInstance()
{
    // 雙重鎖定只需要一句判斷就可以了
    if (_uniqueInstance == null)
    {
        lock (Locker)
        {
            if (_uniqueInstance == null)
            {
                _uniqueInstance = new LockSingleton();
            }
        }
    }
    return _uniqueInstance;
}

3.使用lock

Task.Run(() =>
{
    LockSingleton lockSingleton = LockSingleton.GetInstance();
    Console.WriteLine(lockSingleton.GetHashCode());

});

Task.Run(() =>
{
    LockSingleton lockSingleton = LockSingleton.GetInstance();
    Console.WriteLine(lockSingleton.GetHashCode());
});

輸出結果

over!
6044116
6044116

延遲載入(Lazy)

public class LazySingleton
{
    private static readonly Lazy<LazySingleton> SingletonLazy = new Lazy<LazySingleton>(() => new LazySingleton());

    /// <summary>
    /// 私有建構函式
    /// </summary>
    private LazySingleton()
    {
        Console.WriteLine("我被建立了.Lazy");
    }

    /// <summary>
    /// 獲取例項
    /// </summary>
    /// <returns></returns>
    public static LazySingleton GetInstance()
    {
        return SingletonLazy.Value;
    }
}

總結

單例主要分為如下幾種方式,在實際使用過程中:建議採用延遲載入(Lazy)

餓漢式 懶漢式 懶漢式+lock鎖+雙重判斷 延遲載入(Lazy)
執行緒安全 執行緒不安全 執行緒安全 執行緒安全
不是延遲載入(會浪費記憶體) 會延遲載入 會延遲載入 會延遲載入
沒有加鎖 沒有加鎖 加鎖 加鎖