.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
#endregion
namespace 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)
更加灵活可控,适合 高并发、异步、分布式
等复杂场景下的同步需求。