C#使用雙檢鎖的示例代碼
為什么要使用雙重檢查鎖定(Double-Checked Locking, DCL)”?答案的核心在于:在保證線程安全的前提下,盡可能提高性能。
下面我們從背景、問題、解決方案三個層面來解釋。
一、背景:單例模式 + 多線程環(huán)境
在多線程程序中,如果多個線程同時調(diào)用 getInstance(),而實例尚未創(chuàng)建,就可能出現(xiàn) 多個線程同時進入 if (instance == null) 判斷,從而創(chuàng)建多個實例 —— 這違反了單例的“唯一性”原則。
所以,必須保證線程安全。
二、簡單加鎖的問題:性能瓶頸
最直接的線程安全方案是給整個 getInstance() 方法加鎖:
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
? 安全
? 但每次調(diào)用都要加鎖!即使實例早已創(chuàng)建,后續(xù)所有訪問仍要競爭鎖,性能開銷大。
在高并發(fā)場景下,這會成為明顯的性能瓶頸。
三、雙重檢查鎖定(DCL)的思路
目標(biāo):只在“第一次創(chuàng)建實例時”加鎖,之后直接返回已有實例,避免無謂的同步開銷。
實現(xiàn)邏輯:
- 第一次檢查(無鎖):如果 instance != null,直接返回(絕大多數(shù)情況走這里,快?。?。
- 如果 instance == null,說明可能需要創(chuàng)建,此時加鎖。
- 第二次檢查(有鎖):再次判斷 instance == null,防止多個線程在第一次檢查后都進入臨界區(qū),導(dǎo)致重復(fù)創(chuàng)建。
public static Singleton Instance
{
get
{
if (instance == null) // 第一次檢查(無鎖)
{
lock (lockObj)
{
if (instance == null) // 第二次檢查(有鎖)
instance = new Singleton();
}
}
return instance;
}
}
四、為什么需要“兩次”檢查?
假設(shè)只有一次檢查(只在鎖內(nèi)判斷):
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
}
→ 這樣雖然安全,但每次都要加鎖,失去了懶加載的性能優(yōu)勢。
而如果只在鎖外檢查一次:
if (instance == null)
{
lock (lockObj)
{
instance = new Singleton(); // ? 沒有第二次檢查!
}
}
→ 問題:線程 A 和 B 同時通過 if (instance == null),A 先拿到鎖創(chuàng)建了實例,B 等待后拿到鎖,又創(chuàng)建一次!破壞單例!
? 所以,“雙重檢查”是為了:
- 避免重復(fù)創(chuàng)建(第二次檢查)
- 避免不必要的加鎖(第一次檢查)
五、內(nèi)存可見性問題:為什么需要volatile
在 C#、Java 等語言中,對象創(chuàng)建不是原子操作,可能被重排序:
instance = new Singleton(); // 實際可能分解為: // 1. 分配內(nèi)存 // 2. 初始化對象 // 3. 將引用賦值給 instance
但編譯器或 CPU 可能將步驟 2 和 3 重排序 → 先賦值引用,再初始化!
結(jié)果:線程 A 還沒初始化完,線程 B 就看到 instance != null,拿到一個未完全構(gòu)造的對象,導(dǎo)致程序崩潰!
? 解決方案:使用 volatile 關(guān)鍵字(C# / Java)
- 禁止指令重排序
- 保證內(nèi)存可見性(一個線程寫入,其他線程立即可見)
private static volatile Singleton instance;
?? 在 C++ 中需使用 std::atomic 或內(nèi)存屏障;在 .NET 中 volatile 足夠。
六、現(xiàn)代替代方案:Lazy<T>
雖然 DCL 是經(jīng)典解法,但在現(xiàn)代 C# 中,更推薦使用 Lazy<T>:
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance => lazy.Value;
- 內(nèi)部已實現(xiàn)線程安全的雙重檢查邏輯
- 代碼更簡潔、不易出錯
- 性能同樣優(yōu)秀
但理解 DCL 的原理仍然非常重要,尤其在面試、底層開發(fā)或跨語言場景中。
總結(jié):為什么要用雙檢鎖?
| 目標(biāo) | 說明 |
|---|---|
| ? 線程安全 | 防止多線程創(chuàng)建多個實例 |
| ? 懶加載 | 實例只在首次使用時創(chuàng)建 |
| ? 高性能 | 實例創(chuàng)建后,后續(xù)訪問無需加鎖 |
| ? 避免重排序風(fēng)險 | 配合 volatile 保證正確性 |
?? 一句話總結(jié):雙重檢查鎖是在“懶加載 + 線程安全 + 高性能”三者之間取得的最佳平衡(在沒有 Lazy<T> 的年代尤其重要)。
如果你在寫 C#,可以直接用 Lazy<T>;但理解 DCL 背后的并發(fā)思想,對成為一名優(yōu)秀開發(fā)者至關(guān)重要。這是一個非常好的問題!“為什么要使用雙重檢查鎖定(Double-Checked Locking, DCL)”?答案的核心在于:在保證線程安全的前提下,盡可能提高性能。
下面我們從背景、問題、解決方案三個層面來解釋。
一、背景:單例模式 + 多線程環(huán)境
在多線程程序中,如果多個線程同時調(diào)用 getInstance(),而實例尚未創(chuàng)建,就可能出現(xiàn) 多個線程同時進入 if (instance == null) 判斷,從而創(chuàng)建多個實例 —— 這違反了單例的“唯一性”原則。
所以,必須保證線程安全。
二、簡單加鎖的問題:性能瓶頸
最直接的線程安全方案是給整個 getInstance() 方法加鎖:
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
? 安全
? 但每次調(diào)用都要加鎖!即使實例早已創(chuàng)建,后續(xù)所有訪問仍要競爭鎖,性能開銷大。
在高并發(fā)場景下,這會成為明顯的性能瓶頸。
三、雙重檢查鎖定(DCL)的思路
目標(biāo):只在“第一次創(chuàng)建實例時”加鎖,之后直接返回已有實例,避免無謂的同步開銷。
實現(xiàn)邏輯:
- 第一次檢查(無鎖):如果 instance != null,直接返回(絕大多數(shù)情況走這里,快?。?。
- 如果 instance == null,說明可能需要創(chuàng)建,此時加鎖。
- 第二次檢查(有鎖):再次判斷 instance == null,防止多個線程在第一次檢查后都進入臨界區(qū),導(dǎo)致重復(fù)創(chuàng)建。
public static Singleton Instance
{
get
{
if (instance == null) // 第一次檢查(無鎖)
{
lock (lockObj)
{
if (instance == null) // 第二次檢查(有鎖)
instance = new Singleton();
}
}
return instance;
}
}
四、為什么需要“兩次”檢查?
假設(shè)只有一次檢查(只在鎖內(nèi)判斷):
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
}
→ 這樣雖然安全,但每次都要加鎖,失去了懶加載的性能優(yōu)勢。
而如果只在鎖外檢查一次:
if (instance == null)
{
lock (lockObj)
{
instance = new Singleton(); // ? 沒有第二次檢查!
}
}
→ 問題:線程 A 和 B 同時通過 if (instance == null),A 先拿到鎖創(chuàng)建了實例,B 等待后拿到鎖,又創(chuàng)建一次!破壞單例!
? 所以,“雙重檢查”是為了:
- 避免重復(fù)創(chuàng)建(第二次檢查)
- 避免不必要的加鎖(第一次檢查)
五、內(nèi)存可見性問題:為什么需要volatile
在 C#、Java 等語言中,對象創(chuàng)建不是原子操作,可能被重排序:
instance = new Singleton(); // 實際可能分解為: // 1. 分配內(nèi)存 // 2. 初始化對象 // 3. 將引用賦值給 instance
但編譯器或 CPU 可能將步驟 2 和 3 重排序 → 先賦值引用,再初始化!
結(jié)果:線程 A 還沒初始化完,線程 B 就看到 instance != null,拿到一個未完全構(gòu)造的對象,導(dǎo)致程序崩潰!
? 解決方案:使用 volatile 關(guān)鍵字(C# / Java)
- 禁止指令重排序
- 保證內(nèi)存可見性(一個線程寫入,其他線程立即可見)
private static volatile Singleton instance;
?? 在 C++ 中需使用 std::atomic 或內(nèi)存屏障;在 .NET 中 volatile 足夠。
六、現(xiàn)代替代方案:Lazy<T>
雖然 DCL 是經(jīng)典解法,但在現(xiàn)代 C# 中,更推薦使用 Lazy<T>:
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance => lazy.Value;
- 內(nèi)部已實現(xiàn)線程安全的雙重檢查邏輯
- 代碼更簡潔、不易出錯
- 性能同樣優(yōu)秀
但理解 DCL 的原理仍然非常重要,尤其在面試、底層開發(fā)或跨語言場景中。
總結(jié):為什么要用雙檢鎖?
| 目標(biāo) | 說明 |
|---|---|
| ? 線程安全 | 防止多線程創(chuàng)建多個實例 |
| ? 懶加載 | 實例只在首次使用時創(chuàng)建 |
| ? 高性能 | 實例創(chuàng)建后,后續(xù)訪問無需加鎖 |
| ? 避免重排序風(fēng)險 | 配合 volatile 保證正確性 |
?? 一句話總結(jié):雙重檢查鎖是在“懶加載 + 線程安全 + 高性能”三者之間取得的最佳平衡(在沒有 Lazy<T> 的年代尤其重要)。
到此這篇關(guān)于C#使用雙檢鎖的示例代碼的文章就介紹到這了,更多相關(guān)C# 雙檢鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用Spire.Doc實現(xiàn)將Word文檔轉(zhuǎn)換為XML
將 Word 文檔轉(zhuǎn)換為 XML 并非簡單的格式轉(zhuǎn)換,其背后蘊含著巨大的業(yè)務(wù)價值和技術(shù)優(yōu)勢,下面我們就來看看如何使用C#實現(xiàn)Word文檔轉(zhuǎn)換為XML吧2025-10-10
C#使用Tesseract進行Ocr識別的方法實現(xiàn)
本文主要介紹了C#使用Tesseract進行Ocr識別的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06

