建立型-單例模式 SingletonPattern
阿新 • • 發佈:2021-01-09
單例模式 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) |
---|---|---|---|
執行緒安全 | 執行緒不安全 | 執行緒安全 | 執行緒安全 |
不是延遲載入(會浪費記憶體) | 會延遲載入 | 會延遲載入 | 會延遲載入 |
沒有加鎖 | 沒有加鎖 | 加鎖 | 加鎖 |