面試翻車后才知道:原來 .NET 的“鎖”不止 lock
最近有個(gè)朋友去面試一家公司的高級(jí) .NET 開發(fā)崗位,被問了一個(gè)看起來特別基礎(chǔ)的問題:
“在多線程開發(fā)中,你都用過哪些鎖機(jī)制?”
他想了想,答得挺標(biāo)準(zhǔn):
“我平時(shí)主要用 lock 關(guān)鍵字,保護(hù)一些共享資源,比如靜態(tài)變量或者緩存。一般會(huì)定義一個(gè)私有的靜態(tài)對(duì)象,然后在需要同步的地方加上 lock(obj)。”
還當(dāng)場(chǎng)寫了個(gè)例子:
private staticreadonlyobject _lock = newobject();
privatestaticint _counter = 0;
public void Increment()
{
lock (_lock)
{
_counter++;
}
}面試官點(diǎn)點(diǎn)頭,沒說什么,接著追問:
“那除了 lock,你還了解別的線程同步方式嗎?比如 Mutex、SemaphoreSlim,或者 ReaderWriterLockSlim?”
這下他卡殼了,沉默幾秒后老實(shí)承認(rèn):
“這些名字我聽過,但項(xiàng)目里沒怎么用過,不太熟。”
面試官笑了笑,沒為難他,反而溫和地說:
“l(fā)ock 確實(shí)是最常用也最安全的方式之一,但它只是 .NET 并發(fā)工具箱里的一小部分。在高并發(fā)、異步編程,或者讀寫頻率差異大的場(chǎng)景下,其他機(jī)制可能更合適。”
這場(chǎng)對(duì)話雖然不長,卻戳中了一個(gè)很普遍的問題:
很多 .NET 開發(fā)者對(duì)多線程的理解,還停留在只會(huì)用 lock 的階段。
面試官為什么愛問“鎖”?
你以為他只是想考你背不背得出幾個(gè)類名?其實(shí)不是。
這類問題背后的真正意圖是:
- 你有沒有基本的線程安全意識(shí)?
- 遇到并發(fā)問題時(shí),能不能根據(jù)場(chǎng)景選對(duì)工具?
- 是否具備一定的性能優(yōu)化思維?
- 對(duì) .NET 的并發(fā)模型有沒有主動(dòng)了解過?
如果你張口就是“我只用 lock”,雖然沒錯(cuò),但顯得眼界窄了點(diǎn),缺乏深度思考。
面試官聽到這種回答,心里大概會(huì)想:“這人可能只會(huì)寫業(yè)務(wù)代碼,沒碰過復(fù)雜并發(fā)場(chǎng)景。”
.NET 常見的鎖和同步機(jī)制(面試加分項(xiàng))
為了幫大家避開這個(gè)“知識(shí)盲區(qū)”,下面整理了 .NET 中常用的幾種同步機(jī)制,以及它們各自的適用場(chǎng)景。建議記一記,面試能加分。
1. lock / Monitor
- 特點(diǎn):語法簡單,自動(dòng)釋放,適合保護(hù)臨界區(qū)。
- 底層原理:lock 其實(shí)是 Monitor.Enter 和 Monitor.Exit 的語法糖,編譯器還會(huì)自動(dòng)加上 try-finally,防止忘記解鎖。
- 擴(kuò)展能力:Monitor 本身還支持 Wait、Pulse、TryEnter 等高級(jí)操作,比 lock 更靈活。
- 注意事項(xiàng):
別鎖 this,容易被外部誤用;
別鎖字符串常量,因?yàn)樽址旭v留機(jī)制,可能導(dǎo)致不同地方共用一把鎖;
別鎖 typeof(SomeType),也容易出問題。
- 適用場(chǎng)景:通用的臨界資源保護(hù),最常見也最穩(wěn)妥。
lock (_lockObj)
{
// 臨界區(qū)操作
}2. ReaderWriterLockSlim
- 特點(diǎn):允許多個(gè)線程同時(shí)讀,但寫的時(shí)候必須獨(dú)占。
- 優(yōu)勢(shì):在“讀多寫少”的場(chǎng)景下,性能遠(yuǎn)超 lock。
- 典型場(chǎng)景:緩存讀取、配置中心、全局狀態(tài)管理等。
- 注意:使用時(shí)一定要配 try-finally,確保解鎖。
_rwLock.EnterReadLock();
try
{
// 讀取共享數(shù)據(jù)
}
finally
{
_rwLock.ExitReadLock();
}寫的時(shí)候:
_rwLock.EnterWriteLock();
try
{
// 修改共享數(shù)據(jù)
}
finally
{
_rwLock.ExitWriteLock();
}3. SemaphoreSlim
- 特點(diǎn):控制同時(shí)訪問某個(gè)資源的線程數(shù)量,支持異步。
- 最大亮點(diǎn):它有 WaitAsync() 方法,可以在 async/await 中安全使用。
- 妙用技巧:設(shè)置信號(hào)量最大為 1,就能實(shí)現(xiàn)一個(gè)“異步鎖”。
- 適用場(chǎng)景:限流、資源池控制、避免線程池耗盡。
await _semaphore.WaitAsync();
try
{
// 臨界區(qū)
}
finally
{
_semaphore.Release();
}特別提醒:lock 在 async 方法里不能直接用(會(huì)報(bào)錯(cuò)或死鎖),這時(shí)候就得靠 SemaphoreSlim 救場(chǎng)。
4. Mutex
- 特點(diǎn):系統(tǒng)級(jí)鎖,支持跨進(jìn)程。
- 優(yōu)勢(shì):可以用它實(shí)現(xiàn)“程序只能運(yùn)行一個(gè)實(shí)例”。
- 缺點(diǎn):每次加鎖都會(huì)進(jìn)入內(nèi)核態(tài),性能開銷大,不適合高頻調(diào)用。
- 適用場(chǎng)景:防止程序多開、進(jìn)程間通信同步。
using (var mutex = new Mutex(false, "MyApp.Unique"))
{
if (!mutex.WaitOne(0))
{
Console.WriteLine("程序已在運(yùn)行!");
return;
}
// 主程序邏輯
}小知識(shí):Mutex 名字來源于 "Mutual Exclusion",也就是互斥。
5. Interlocked
- 特點(diǎn):無鎖原子操作,基于 CPU 指令完成。
- 優(yōu)點(diǎn):速度快到飛起,沒有上下文切換,適合超高頻計(jì)數(shù)。
- 常見用途:計(jì)數(shù)器、狀態(tài)標(biāo)志位、CAS(Compare-and-Swap)操作。
Interlocked.Increment(ref _counter); // ++操作原子化
Interlocked.Exchange(ref _flag, 1); // 原子賦值
Interlocked.CompareExchange(ref _value, newValue, oldValue); // CAS這個(gè)類在高性能庫中很常見,比如內(nèi)存隊(duì)列、狀態(tài)機(jī)等。
6. SpinLock
- 特點(diǎn):自旋等待,不釋放 CPU 時(shí)間片。
- 風(fēng)險(xiǎn)提示:如果臨界區(qū)執(zhí)行時(shí)間稍長,就會(huì)白白浪費(fèi) CPU 資源。
- 絕對(duì)禁忌:不能用于異步方法,也不能在它里面 await。
- 適用場(chǎng)景:極短的操作,比如高性能庫內(nèi)部的小型同步。
bool lockTaken = false;
_spinLock.Enter(ref lockTaken);
try
{
// 快速操作,越快越好
}
finally
{
if (lockTaken) _spinLock.Exit();
}普通業(yè)務(wù)開發(fā)基本用不到,除非你在寫底層框架。
7. 其他容易被忽略的同步工具
這些不是“鎖”,但在并發(fā)編程中非常有用:
- ManualResetEventSlim / AutoResetEvent基于信號(hào)的同步機(jī)制,適合一個(gè)線程通知另一個(gè)線程“我可以繼續(xù)了”。
- CountdownEvent等待多個(gè)任務(wù)完成后再繼續(xù),比如啟動(dòng) N 個(gè)線程干活,等它們?nèi)拷Y(jié)束再收尾。
- Barrier讓多個(gè)線程在某個(gè)階段“齊步走”,常用于并行算法或階段性同步。
- 并發(fā)集合(如 ConcurrentDictionary、BlockingCollection<T>)很多時(shí)候你根本不需要手動(dòng)加鎖!這些集合內(nèi)部已經(jīng)做好了線程安全處理,性能還更好。
比如你要做一個(gè)線程安全的緩存,直接上 ConcurrentDictionary,比自己 lock + Dictionary 強(qiáng)多了。
面試怎么答?教你幾句話拿下面試官
下次再被問“你用過哪些鎖?”,別再說“我就用過 lock”了。
推薦這樣說:
“在實(shí)際項(xiàng)目中,我主要使用 lock 來保護(hù)共享資源,因?yàn)樗唵巍踩⒉蝗菀壮鲥e(cuò)。但在讀多寫少的場(chǎng)景下,比如緩存管理,我會(huì)考慮用 ReaderWriterLockSlim 來提升并發(fā)性能。如果是在異步方法里,lock 不支持 await,我會(huì)用 SemaphoreSlim(1,1) 來實(shí)現(xiàn)異步互斥。此外,我也了解 Interlocked 用于原子操作,Mutex 可以跨進(jìn)程同步。雖然項(xiàng)目中用得不多,但我清楚它們各自的適用場(chǎng)景和優(yōu)缺點(diǎn)。”
這番話聽起來就很專業(yè):
? 有實(shí)踐經(jīng)驗(yàn)
? 有技術(shù)廣度
? 有場(chǎng)景判斷能力
? 還很誠實(shí),不裝懂
給所有 .NET 開發(fā)者的幾點(diǎn)建議
- lock 是起點(diǎn),不是終點(diǎn)它好用,但不代表它是萬能的。別把它當(dāng)唯一解。
- 理解場(chǎng)景比死記語法更重要多問問自己:我現(xiàn)在是讀多寫少?還是高并發(fā)?要不要支持異步?有沒有跨進(jìn)程需求?
- 異步編程中,lock 失效了怎么辦?務(wù)必掌握 SemaphoreSlim 和 async 友好的同步方式,否則遲早踩坑。
- 善用并發(fā)集合很多時(shí)候你根本不需要自己加鎖。ConcurrentDictionary、ConcurrentQueue、BlockingCollection 都是現(xiàn)成的好工具。
結(jié)語
一次看似簡單的面試提問,可能暴露出的是你整個(gè)并發(fā)知識(shí)體系的短板。
在 .NET 世界里,lock 固然重要,但真正體現(xiàn)你技術(shù)深度的,是你知不知道還有別的選擇,以及為什么選它。
與其等到面試被問住時(shí)才懊悔“我沒學(xué)過”,不如現(xiàn)在就開始系統(tǒng)梳理這些同步機(jī)制。
做到心中有“鎖”,才能在關(guān)鍵時(shí)刻臨危不亂。




























