來自:阿里的 通義靈碼
在C#中,單例模式實現線程安全時通常會採用雙重檢查鎖定(Double-Checked Locking, DCL)的策略來確保只創建類的一個實例,並且這個過程是線程安全的。
兩層lock
的設計是爲了減少獲取鎖的開銷,尤其是當類的實例已經被初始化的情況下。
以下是一個使用雙重檢查鎖定的C#單例模式示例:
public sealed class Singleton { // 創建一個靜態變量來保存唯一的 Singleton 實例。 private static volatile Singleton instance; // 創建一個對象用於同步訪問Singleton實例。 private static object syncRoot = new Object(); // 私有構造函數防止其他代碼通過new關鍵字實例化。 private Singleton() {} // 提供一個公共的靜態方法來返回 Singleton 實例。 public static Singleton Instance { get { // 第一層檢查:如果實例已存在,則直接返回,無需加鎖。 if (instance != null) { return instance; } // 第二層檢查:只有當第一次進入這裏,即實例未被初始化時才需要加鎖。 lock (syncRoot) { // 在鎖內再次檢查實例是否已被另一個線程初始化。 // 這是因爲,在第一個if判斷和進入lock之間可能存在競態條件。 if (instance == null) { // 只有在這裏創建實例。 instance = new Singleton(); } return instance; } } } }
爲什麼需要兩層檢查?
- 性能優化:
- 第一次檢查
instance != null
可以在不加鎖的情況下完成,如果實例已經初始化,那麼大多數情況下可以直接返回實例,避免了不必要的鎖操作帶來的性能損耗。
- 第一次檢查
- 線程安全性:
- 如果多個線程同時發現實例尚未創建,而只有一個線程能夠進入內部的
lock
塊進行初始化。如果沒有第二次檢查,所有線程都會創建實例,導致違反單例原則。 - 由於CLR的內存模型允許對象的構造和初始化順序與指令執行順序不同步,因此即使在多線程環境中,也需要在
lock
保護下進行實例化以保證線程安全。第二層檢查確保了實例化操作僅執行一次。
- 如果多個線程同時發現實例尚未創建,而只有一個線程能夠進入內部的
總結來說,雙重檢查鎖定的目的在於既保證了線程安全,又儘量減少了對鎖的使用頻率,從而提高了程序的併發性能。