.NET 9
引入了全新的 System.Threading.Lock
類型,作為更現代、類型安全且具備遞歸支持的同步原語。與傳統的基于 Monitor.Enter/lock(obj)
的方式不同,Lock
是一個具體的類,提供了更靈活的 API
和結構化編程模型。
- Lock 類
Lock
是一個具體密封類,詳細代碼如下:
#region 程序集 System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.5\ref\net9.0\System.Runtime.dll
#endregionnamespace System.Threading
{//// 摘要:// Provides a mechanism for achieving mutual exclusion in regions of code between// different threads.public sealed class Lock{//// 摘要:// Initializes a new instance of the System.Threading.Lock class.public Lock();//// 摘要:// Gets a value that indicates whether the lock is held by the current thread.//// 返回結果:// true if the current thread holds the lock; otherwise, false.public bool IsHeldByCurrentThread { get; }//// 摘要:// Enters the lock, waiting if necessary until the lock can be entered.//// 異常:// T:System.Threading.LockRecursionException:// The lock has reached the limit of repeated entries by the current thread. The// limit is implementation-defined and is intended to be high enough that it would// not be reached in normal situations.public void Enter();//// 摘要:// Enters the lock, waiting if necessary until the lock can be entered.//// 返回結果:// A System.Threading.Lock.Scope that can be disposed to exit the lock.//// 異常:// T:System.Threading.LockRecursionException:// The lock has reached the limit of repeated entries by the current thread. The// limit is implementation-defined and is intended to be high enough that it would// not be reached in normal situations.public Scope EnterScope();//// 摘要:// Exits the lock.//// 異常:// T:System.Threading.SynchronizationLockException:// The current thread does not hold the lock.public void Exit();//// 摘要:// Tries to enter the lock without waiting.//// 返回結果:// true if the lock was entered by the current thread; otherwise, false.//// 異常:// T:System.Threading.LockRecursionException:// The lock has reached the limit of repeated entries by the current thread. The// limit is implementation-defined and is intended to be high enough that it would// not be reached in normal situations.public bool TryEnter();//// 摘要:// Tries to enter the lock, waiting if necessary for the specified number of milliseconds// until the lock can be entered.//// 參數:// millisecondsTimeout:// The number of milliseconds to wait until the lock can be entered. Specify Timeout.Infinite// (//// -1//// ) to wait indefinitely, or//// 0//// to not wait.//// 返回結果:// true if the lock was entered by the current thread; otherwise, false.//// 異常:// T:System.ArgumentOutOfRangeException:// millisecondsTimeout is less than//// -1//// .//// T:System.Threading.LockRecursionException:// The lock has reached the limit of repeated entries by the current thread. The// limit is implementation-defined and is intended to be high enough that it would// not be reached in normal situations.public bool TryEnter(int millisecondsTimeout);//// 摘要:// Tries to enter the lock, waiting if necessary until the lock can be entered or// until the specified timeout expires.//// 參數:// timeout:// A System.TimeSpan that represents the number of milliseconds to wait until the// lock can be entered. Specify a value that represents Timeout.Infinite (//// -1//// ) milliseconds to wait indefinitely, or a value that represents//// 0//// milliseconds to not wait.//// 返回結果:// true if the lock was entered by the current thread; otherwise, false.//// 異常:// T:System.ArgumentOutOfRangeException:// timeout, after its conversion to an integer millisecond value, represents a value// that is less than//// -1//// milliseconds or greater than Int32.MaxValue milliseconds.//// T:System.Threading.LockRecursionException:// The lock has reached the limit of repeated entries by the current thread. The// limit is implementation-defined and is intended to be high enough that it would// not be reached in normal situations.public bool TryEnter(TimeSpan timeout);//// 摘要:// Represents a System.Threading.Lock that might have been entered.public ref struct Scope{//// 摘要:// Exits the lock if the System.Threading.Lock.Scope represents a lock that was// entered.//// 異常:// T:System.Threading.SynchronizationLockException:// The System.Threading.Lock.Scope represents a lock that was entered and the current// thread does not hold the lock.public void Dispose();}}
}
🔒 Lock 對象概述
System.Threading.Lock
是一種 輕量級互斥鎖(Lightweight Mutex
),用于控制多線程對共享資源的訪問,確保同一時間只有一個線程可以執行特定代碼段
。
它適用于需要 顯式管理加鎖和解鎖操作
的場景,比如在 非阻塞編程
中嘗試獲取鎖或使用 超時機制
。
? 特性
- 支持遞歸鎖定(默認允許同一個線程多次進入)
- 提供阻塞、非阻塞和帶超時的獲取方式
- 使用 Scope 結構實現
RAII
風格的自動釋放 - 線程親和性強,可判斷當前線程是否持有鎖
RAII (Resource Acquisition Is Initialization)
是一種 C++
編程范式,其核心思想是 將資源的生命周期綁定到對象的生命周期上,即:
- 資源的獲取發生在對象構造時;
- 資源的釋放發生在對象析構時。
在 .NET 9
的 System.Threading.Lock
中,Scope 是一個 ref struct
,通過調用 EnterScope()
方法獲得。它實現了類似 RAII
的模式:
//
// 摘要:
// Represents a System.Threading.Lock that might have been entered.
public ref struct Scope
{//// 摘要:// Exits the lock if the System.Threading.Lock.Scope represents a lock that was// entered.//// 異常:// T:System.Threading.SynchronizationLockException:// The System.Threading.Lock.Scope represents a lock that was entered and the current// thread does not hold the lock.public void Dispose();
}// 使用 EnterScope()
using (var scope = myLock.EnterScope())
{// 執行臨界區代碼
} // 自動調用 scope.Dispose(),進而釋放鎖
解釋 RAII 風格的自動釋放
使用 Scope 結構實現 RAII 風格的自動釋放
這句話的意思是:
- 當你調用
EnterScope()
獲取一個Scope
實例時,鎖已經被當前線程持有; - 將該
Scope
實例放入using
語句塊中,當代碼塊結束時,會自動調用其Dispose()
方法; - 在
Dispose()
方法內部,會調用Exit()
來釋放鎖; - 這樣就實現了 鎖的獲取和釋放與代碼塊的進入和退出嚴格綁定,避免忘記釋放鎖或異常情況下鎖未被釋放的問題。
這種方式提升了代碼的安全性和可讀性,是現代 .NET
推薦使用的同步編程模型。
📌 構造函數
public Lock();
初始化一個新的 Lock
實例。默認情況下,該鎖是可重入的(recursive
),即同一個線程可以多次調用 Enter()
而不會死鎖。
🧱 主要方法詳解
1. void Enter()
阻塞當前線程直到成功獲取鎖。
var myLock = new Lock();myLock.Enter(); // 阻塞直到獲取鎖
try {// 訪問共享資源
} finally {myLock.Exit();
}
拋出異常:
LockRecursionException
: 如果遞歸次數超過限制(極少發生)
2. Scope EnterScope()
嘗試進入鎖,并返回一個 ref struct Scope
,用于通過 using
自動釋放鎖。
using (myLock.EnterScope())
{// 安全訪問共享資源
}
// 鎖自動釋放
這是推薦的方式,避免手動調用
Exit()
導致的資源泄漏。
3. void Exit()
釋放鎖。必須由當前持有鎖的線程調用,否則拋出異常。
myLock.Enter();
try {// ...
} finally {myLock.Exit();
}
拋出異常:
SynchronizationLockException
: 當前線程未持有鎖
4. bool TryEnter()
嘗試立即獲取鎖,不等待。
if (myLock.TryEnter())
{try {// 成功獲取鎖} finally {my7.Exit();}
}
else
{// 獲取失敗,跳過
}
返回值:
true
: 成功獲取鎖false
: 鎖已被其他線程占用
5. bool TryEnter(int millisecondsTimeout)
嘗試在指定時間內獲取鎖。
bool lockTaken = myLock.TryEnter(1000); // 最多等待1秒
if (lockTaken)
{try { /* ... */ } finally { myLock.Exit(); }
}
參數:
millisecondsTimeout
: 等待毫秒數,-1
表示無限等待,0
表示不等待
返回值:
true
: 成功獲取鎖false
: 超時或未獲取到
6. bool TryEnter(TimeSpan timeout)
同上,但接受 TimeSpan
參數。
bool lockTaken = myLock.TryEnter(TimeSpan.FromSeconds(2));
🧩 嵌套結構:Lock.Scope
這是一個 ref struct
,用于封裝鎖的生命周期,推薦配合 using
使用。
using var scope = myLock.EnterScope();
// 執行臨界區代碼
當 scope
被 dispose
時,會自動調用 Exit()
。
方法:
public void Dispose();
釋放鎖,若當前線程未持有鎖則拋出異常。
🧠 內部原理簡析
Lock
內部基于高效的自旋鎖(SpinLock
)+ 內核事件(Event
)混合實現。- 初期嘗試自旋幾次以快速獲取鎖,失敗后進入內核等待狀態。
- 支持遞歸鎖定,默認遞歸深度限制較高(足夠日常使用)。
- 使用線程本地存儲記錄當前線程是否持有鎖,保證
IsHeldByCurrentThread
的準確性。
🔍 屬性:bool IsHeldByCurrentThread
檢查當前線程是否持有該鎖。
if (myLock.IsHeldByCurrentThread)
{Console.WriteLine("當前線程已持有鎖");
}
適用于調試和日志記錄,避免重復加鎖導致死鎖。
🧪 應用場景舉例
場景 1:線程安全的計數器
private int _counter = 0;
private readonly Lock _lock = new();public void Increment()
{using (_lock.EnterScope()){_counter++;}
}public int GetCount()
{using (_lock.EnterScope()){return _counter;}
}
場景 2:線程安全的單例實現
- 方式一:原生
Lock
實現
public class Singleton
{// 單例實例private static Singleton _instance;// 鎖對象private static readonly Lock _lock = new();// 私有化無參構造函數private Singleton(){// 初始化邏輯}// 獲取單例實例的方法public static Singleton Instance{get{// 先判斷是否已創建,避免每次都加鎖if (_instance == null){using (_lock.EnterScope()){// 再次檢查是否為 null(雙重檢查鎖定)if (_instance == null){_instance = new Singleton();}}}return _instance;}}// 其他方法實現
}
- 方式二:使用
Lazy<T> & Lock
實現
public class Singleton
{private static readonly Lock _lock = new();private static readonly Lazy<Singleton> _lazyInstance = new(() =>{using (_lock.EnterScope()){return new Singleton();}});// 私有化無參構造函數private Singleton(){// 初始化邏輯}public static Singleton Instance => _lazyInstance.Value;// 其他方法實現
}
場景 3:帶超時的緩存刷新
private readonly Lock _cacheLock = new();
private object _cachedData;public object? GetCachedData()
{if (_cacheLock.TryEnter(TimeSpan.FromSeconds(1))){try{if (_cachedData == null){_cachedData = FetchFromDatabase();}return _cachedData;}finally{_cacheLock.Exit();}}else{// 超時處理邏輯return null;}
}
場景 4:避免跨線程訪問 UI 控件(WinForms)
private readonly Lock _uiLock = new();private void UpdateLabel(string text)
{using (_uiLock.EnterScope()){// 假設 label1 是 WinForm 上的控件if (label1.InvokeRequired){label1.Invoke(new Action(() => label1.Text = text));}else{label1.Text = text;}}
}
?? 注意事項
- 不要跨線程傳遞
Lock.Scope
實例。 - 避免在鎖內部進行長時間操作,影響并發性能。
- 優先使用
EnterScope()
+using
來確保鎖釋放。 - 若需更高性能讀寫分離,請考慮
ReaderWriterLockSlim
。
🧾 總結
特性 | 描述 |
---|---|
類型 | 互斥鎖(Mutex) |
是否遞歸 | 是(默認) |
是否公平 | 否(先進先出無法保證) |
是否托管資源 | 是 |
推薦使用方式 | EnterScope() + using |
System.Threading.Lock
是 .NET 9
中為現代化并發編程設計的新一代同步工具,相比傳統的 lock(obj)
更加靈活可控,適合 高并發、異步、分布式
等復雜場景下的同步需求。