lock对象必须是同一实例,否则无效;禁用new object()、this和字符串字面量;应使用private static readonly object字段;lock不支持async/await;超时需用monitor.tryenter;.net 9起推荐system.threading.lock配合using。

lock 对象必须是同一个实例,否则根本不起作用
常见错误是每次进方法都 new object(),或者直接 lock(this)。这两种写法锁对象不唯一,线程之间无法互斥——等于没加锁。
原因很直接:new object() 每次创建新实例,不同线程拿到的是不同对象;this 是公开引用,外部代码可能也拿它加锁,容易引发死锁或干扰;字符串字面量(如 lock("key"))因字符串驻留机制,可能被跨类共享,同样危险。
正确做法是声明一个 private static readonly object 字段,确保锁对象生命周期与资源访问范围一致:
private static readonly object _syncRoot = new object();
public void UpdateCounter() {
lock (_syncRoot) {
_counter++;
}
}
lock 不能用在 async/await 方法体内部
lock 语句块中不能包含 await 表达式,编译器会直接报错 CS4032:“The 'await' operator cannot be used in a lock statement”。
这是因为 lock 编译后生成的是 try-finally + Monitor.Enter/Exit,而 await 会导致方法被拆成状态机,finally 块无法保证在同一线程上下文中执行,锁可能永远不释放。
替代方案只有两个:
- 把临界区逻辑提前完成(即在
await前做完所有需同步的操作) - 改用
System.Threading.SemaphoreSlim.WaitAsync()或System.Threading.Lock(.NET 9+)等支持异步等待的原语
Monitor.TryEnter 支持超时,lock 不行
当需要“最多等 100ms,超时就放弃”这类逻辑时,lock 完全无能为力——它没有超时参数。
必须切换到 Monitor.TryEnter,并手动管理锁状态:
bool lockTaken = false;
try
{
if (Monitor.TryEnter(_syncRoot, 100))
{
lockTaken = true;
_counter++;
}
else
{
throw new TimeoutException("Failed to acquire lock within 100ms");
}
}
finally
{
if (lockTaken) Monitor.Exit(_syncRoot);
}
注意:这里 Monitor.TryEnter(obj, timeout) 的 timeout 单位是毫秒;若传入 0,则立即返回是否获取成功(类似“try lock”)。
.NET 9 引入了 System.Threading.Lock,性能更好但用法不同
从 .NET 9 和 C# 13 开始,System.Threading.Lock 类型成为专用锁对象,比传统 object + Monitor 更轻量、无分配、无 SyncBlock 开销。
但它不是直接替换 lock 语句的语法糖,而是配合 using 使用:
private readonly Lock _lock = new Lock();
public void UpdateCounter()
{
using (_lock.EnterScope())
{
_counter++;
}
}
关键点:
-
Lock实例是值类型(ref struct),不能作为字段存为object或跨 await 边界传递 - 它不兼容旧式
Monitor.Wait/Pulse,也不支持Monitor.TryEnter那样的超时重载 - 多个共享资源必须用各自独立的
Lock实例,混用会导致锁争用或死锁
老项目升级时最容易忽略的是:Lock 不是 drop-in 替换,它改变了锁对象的生命周期管理和使用契约。











