C#使用雙檢鎖的示例代碼
為什么要使用雙重檢查鎖定(Double-Checked Locking, DCL)”?答案的核心在于:在保證線程安全的前提下,盡可能提高性能。
下面我們從背景、問(wèn)題、解決方案三個(gè)層面來(lái)解釋。
一、背景:?jiǎn)卫J?+ 多線程環(huán)境
在多線程程序中,如果多個(gè)線程同時(shí)調(diào)用 getInstance(),而實(shí)例尚未創(chuàng)建,就可能出現(xiàn) 多個(gè)線程同時(shí)進(jìn)入 if (instance == null) 判斷,從而創(chuàng)建多個(gè)實(shí)例 —— 這違反了單例的“唯一性”原則。
所以,必須保證線程安全。
二、簡(jiǎn)單加鎖的問(wèn)題:性能瓶頸
最直接的線程安全方案是給整個(gè) getInstance() 方法加鎖:
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
? 安全
? 但每次調(diào)用都要加鎖!即使實(shí)例早已創(chuàng)建,后續(xù)所有訪問(wèn)仍要競(jìng)爭(zhēng)鎖,性能開(kāi)銷大。
在高并發(fā)場(chǎng)景下,這會(huì)成為明顯的性能瓶頸。
三、雙重檢查鎖定(DCL)的思路
目標(biāo):只在“第一次創(chuàng)建實(shí)例時(shí)”加鎖,之后直接返回已有實(shí)例,避免無(wú)謂的同步開(kāi)銷。
實(shí)現(xiàn)邏輯:
- 第一次檢查(無(wú)鎖):如果 instance != null,直接返回(絕大多數(shù)情況走這里,快?。?/li>
- 如果 instance == null,說(shuō)明可能需要?jiǎng)?chuàng)建,此時(shí)加鎖。
- 第二次檢查(有鎖):再次判斷 instance == null,防止多個(gè)線程在第一次檢查后都進(jìn)入臨界區(qū),導(dǎo)致重復(fù)創(chuàng)建。
public static Singleton Instance
{
get
{
if (instance == null) // 第一次檢查(無(wú)鎖)
{
lock (lockObj)
{
if (instance == null) // 第二次檢查(有鎖)
instance = new Singleton();
}
}
return instance;
}
}
四、為什么需要“兩次”檢查?
假設(shè)只有一次檢查(只在鎖內(nèi)判斷):
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
}
→ 這樣雖然安全,但每次都要加鎖,失去了懶加載的性能優(yōu)勢(shì)。
而如果只在鎖外檢查一次:
if (instance == null)
{
lock (lockObj)
{
instance = new Singleton(); // ? 沒(méi)有第二次檢查!
}
}
→ 問(wèn)題:線程 A 和 B 同時(shí)通過(guò) if (instance == null),A 先拿到鎖創(chuàng)建了實(shí)例,B 等待后拿到鎖,又創(chuàng)建一次!破壞單例!
? 所以,“雙重檢查”是為了:
- 避免重復(fù)創(chuàng)建(第二次檢查)
- 避免不必要的加鎖(第一次檢查)
五、內(nèi)存可見(jiàn)性問(wèn)題:為什么需要volatile
在 C#、Java 等語(yǔ)言中,對(duì)象創(chuàng)建不是原子操作,可能被重排序:
instance = new Singleton(); // 實(shí)際可能分解為: // 1. 分配內(nèi)存 // 2. 初始化對(duì)象 // 3. 將引用賦值給 instance
但編譯器或 CPU 可能將步驟 2 和 3 重排序 → 先賦值引用,再初始化!
結(jié)果:線程 A 還沒(méi)初始化完,線程 B 就看到 instance != null,拿到一個(gè)未完全構(gòu)造的對(duì)象,導(dǎo)致程序崩潰!
? 解決方案:使用 volatile 關(guān)鍵字(C# / Java)
- 禁止指令重排序
- 保證內(nèi)存可見(jiàn)性(一個(gè)線程寫(xiě)入,其他線程立即可見(jiàn))
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)部已實(shí)現(xiàn)線程安全的雙重檢查邏輯
- 代碼更簡(jiǎn)潔、不易出錯(cuò)
- 性能同樣優(yōu)秀
但理解 DCL 的原理仍然非常重要,尤其在面試、底層開(kāi)發(fā)或跨語(yǔ)言場(chǎng)景中。
總結(jié):為什么要用雙檢鎖?
| 目標(biāo) | 說(shuō)明 |
|---|---|
| ? 線程安全 | 防止多線程創(chuàng)建多個(gè)實(shí)例 |
| ? 懶加載 | 實(shí)例只在首次使用時(shí)創(chuàng)建 |
| ? 高性能 | 實(shí)例創(chuàng)建后,后續(xù)訪問(wèn)無(wú)需加鎖 |
| ? 避免重排序風(fēng)險(xiǎn) | 配合 volatile 保證正確性 |
?? 一句話總結(jié):雙重檢查鎖是在“懶加載 + 線程安全 + 高性能”三者之間取得的最佳平衡(在沒(méi)有 Lazy<T> 的年代尤其重要)。
如果你在寫(xiě) C#,可以直接用 Lazy<T>;但理解 DCL 背后的并發(fā)思想,對(duì)成為一名優(yōu)秀開(kāi)發(fā)者至關(guān)重要。這是一個(gè)非常好的問(wèn)題!“為什么要使用雙重檢查鎖定(Double-Checked Locking, DCL)”?答案的核心在于:在保證線程安全的前提下,盡可能提高性能。
下面我們從背景、問(wèn)題、解決方案三個(gè)層面來(lái)解釋。
一、背景:?jiǎn)卫J?+ 多線程環(huán)境
在多線程程序中,如果多個(gè)線程同時(shí)調(diào)用 getInstance(),而實(shí)例尚未創(chuàng)建,就可能出現(xiàn) 多個(gè)線程同時(shí)進(jìn)入 if (instance == null) 判斷,從而創(chuàng)建多個(gè)實(shí)例 —— 這違反了單例的“唯一性”原則。
所以,必須保證線程安全。
二、簡(jiǎn)單加鎖的問(wèn)題:性能瓶頸
最直接的線程安全方案是給整個(gè) getInstance() 方法加鎖:
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
? 安全
? 但每次調(diào)用都要加鎖!即使實(shí)例早已創(chuàng)建,后續(xù)所有訪問(wèn)仍要競(jìng)爭(zhēng)鎖,性能開(kāi)銷大。
在高并發(fā)場(chǎng)景下,這會(huì)成為明顯的性能瓶頸。
三、雙重檢查鎖定(DCL)的思路
目標(biāo):只在“第一次創(chuàng)建實(shí)例時(shí)”加鎖,之后直接返回已有實(shí)例,避免無(wú)謂的同步開(kāi)銷。
實(shí)現(xiàn)邏輯:
- 第一次檢查(無(wú)鎖):如果 instance != null,直接返回(絕大多數(shù)情況走這里,快?。?/li>
- 如果 instance == null,說(shuō)明可能需要?jiǎng)?chuàng)建,此時(shí)加鎖。
- 第二次檢查(有鎖):再次判斷 instance == null,防止多個(gè)線程在第一次檢查后都進(jìn)入臨界區(qū),導(dǎo)致重復(fù)創(chuàng)建。
public static Singleton Instance
{
get
{
if (instance == null) // 第一次檢查(無(wú)鎖)
{
lock (lockObj)
{
if (instance == null) // 第二次檢查(有鎖)
instance = new Singleton();
}
}
return instance;
}
}
四、為什么需要“兩次”檢查?
假設(shè)只有一次檢查(只在鎖內(nèi)判斷):
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
}
→ 這樣雖然安全,但每次都要加鎖,失去了懶加載的性能優(yōu)勢(shì)。
而如果只在鎖外檢查一次:
if (instance == null)
{
lock (lockObj)
{
instance = new Singleton(); // ? 沒(méi)有第二次檢查!
}
}
→ 問(wèn)題:線程 A 和 B 同時(shí)通過(guò) if (instance == null),A 先拿到鎖創(chuàng)建了實(shí)例,B 等待后拿到鎖,又創(chuàng)建一次!破壞單例!
? 所以,“雙重檢查”是為了:
- 避免重復(fù)創(chuàng)建(第二次檢查)
- 避免不必要的加鎖(第一次檢查)
五、內(nèi)存可見(jiàn)性問(wèn)題:為什么需要volatile
在 C#、Java 等語(yǔ)言中,對(duì)象創(chuàng)建不是原子操作,可能被重排序:
instance = new Singleton(); // 實(shí)際可能分解為: // 1. 分配內(nèi)存 // 2. 初始化對(duì)象 // 3. 將引用賦值給 instance
但編譯器或 CPU 可能將步驟 2 和 3 重排序 → 先賦值引用,再初始化!
結(jié)果:線程 A 還沒(méi)初始化完,線程 B 就看到 instance != null,拿到一個(gè)未完全構(gòu)造的對(duì)象,導(dǎo)致程序崩潰!
? 解決方案:使用 volatile 關(guān)鍵字(C# / Java)
- 禁止指令重排序
- 保證內(nèi)存可見(jiàn)性(一個(gè)線程寫(xiě)入,其他線程立即可見(jiàn))
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)部已實(shí)現(xiàn)線程安全的雙重檢查邏輯
- 代碼更簡(jiǎn)潔、不易出錯(cuò)
- 性能同樣優(yōu)秀
但理解 DCL 的原理仍然非常重要,尤其在面試、底層開(kāi)發(fā)或跨語(yǔ)言場(chǎng)景中。
總結(jié):為什么要用雙檢鎖?
| 目標(biāo) | 說(shuō)明 |
|---|---|
| ? 線程安全 | 防止多線程創(chuàng)建多個(gè)實(shí)例 |
| ? 懶加載 | 實(shí)例只在首次使用時(shí)創(chuàng)建 |
| ? 高性能 | 實(shí)例創(chuàng)建后,后續(xù)訪問(wèn)無(wú)需加鎖 |
| ? 避免重排序風(fēng)險(xiǎn) | 配合 volatile 保證正確性 |
?? 一句話總結(jié):雙重檢查鎖是在“懶加載 + 線程安全 + 高性能”三者之間取得的最佳平衡(在沒(méi)有 Lazy<T> 的年代尤其重要)。
到此這篇關(guān)于C#使用雙檢鎖的示例代碼的文章就介紹到這了,更多相關(guān)C# 雙檢鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#面向?qū)ο笤O(shè)計(jì)原則之組合/聚合復(fù)用原則
這篇文章介紹了C#面向?qū)ο笤O(shè)計(jì)原則之組合/聚合復(fù)用原則,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
C#靜態(tài)構(gòu)造函數(shù)用法實(shí)例分析
這篇文章主要介紹了C#靜態(tài)構(gòu)造函數(shù)用法,以實(shí)例形式較為詳細(xì)的分析了C#靜態(tài)構(gòu)造函數(shù)的用途、實(shí)現(xiàn)方法及使用技巧,需要的朋友可以參考下2015-06-06
C#確保只有一個(gè)實(shí)例在運(yùn)行的方法
這篇文章主要介紹了C#確保只有一個(gè)實(shí)例在運(yùn)行的方法,涉及C#進(jìn)程操作的相關(guān)技巧,需要的朋友可以參考下2015-05-05
C#使用LibUsbDotNet實(shí)現(xiàn)USB設(shè)備檢測(cè)
C#?LibUsbDotNet是一個(gè).NET平臺(tái)上的庫(kù),用于訪問(wèn)USB設(shè)備,它封裝了Linux下的libusb庫(kù),LibUsbDotNet提供了一個(gè)簡(jiǎn)單的API,開(kāi)發(fā)者可以利用這個(gè)庫(kù)在C#中操作USB設(shè)備,本文介紹了C#使用LibUsbDotNet實(shí)現(xiàn)USB設(shè)備檢測(cè),需要的朋友可以參考下2024-07-07
C#簡(jiǎn)單嵌套flash讀取數(shù)據(jù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了C#簡(jiǎn)單嵌套flash讀取數(shù)據(jù)的實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-11-11
C#判斷字符串中內(nèi)容是否為純數(shù)字的詳細(xì)教程
在進(jìn)行C#編程時(shí)候,有的時(shí)候我們需要判斷一個(gè)字符串是否是數(shù)字字符串,下面這篇文章主要給大家介紹了關(guān)于C#判斷字符串中內(nèi)容是否為純數(shù)字的詳細(xì)教程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
Unity3D Shader實(shí)現(xiàn)動(dòng)態(tài)屏幕遮罩
這篇文章主要為大家詳細(xì)介紹了Unity3D Shader實(shí)現(xiàn)動(dòng)態(tài)屏幕遮罩效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02

