lock关键字必须作用于引用类型对象,不能直接用于int、bool等值类型;应使用私有只读object字段作锁,避免用this或公共成员;lock不解决锁外竞态、跨进程同步或async/await场景,后者需SemaphoreSlim等替代方案。

lock 关键字必须作用于引用类型对象
直接对 int、bool 或值类型变量加 lock 会编译失败,因为 lock 要求表达式结果是引用类型。常见错误写法:
int counter = 0;
lock (counter) { /* 编译错误:不能将 int 用作 lock 表达式 */ }正确做法是声明一个专用的私有只读对象字段:private readonly object _lockObj = new object();
// ...
lock (_lockObj) { counter++; }别用 this、typeof(MyClass) 或公共字段——它们可能被外部代码锁定,导致死锁或意外阻塞。
lock 不是万能的,它只保证临界区串行执行
lock 只确保同一把锁对象保护的代码块不会被多个线程同时进入,但它不解决以下问题:
- 锁外的共享变量读写仍可能引发竞态(比如先判断再修改的“检查-执行”逻辑)
- 锁粒度太粗会严重拖慢吞吐;太细则容易漏锁或重复锁
- 无法跨进程同步,也不能替代异步等待(
await内部不能用lock)
if (list.Count == 0) {
lock (_lockObj) {
if (list.Count == 0) { // 必须双重检查!
list.Add(item);
}
}
}漏掉内层判断,就可能在两次 Count 读取之间被其他线程插入元素,导致重复添加。lock 和 Monitor.Enter/Exit 的关系
lock (obj) { ... } 是语法糖,编译后等价于调用 Monitor.Enter(obj) 和 Monitor.Exit(obj),并包裹在 try/finally 中。这意味着:
- 即使临界区内抛出异常,锁也会被释放(这是
lock安全的核心) - 手动调用
Monitor.Enter时若忘记配对Monitor.Exit,会导致永久死锁 -
Monitor.TryEnter(obj, timeout)可实现带超时的获取锁,而lock不支持
Monitor.Wait/Pulse),否则优先用 lock。lock 与 async/await 不能共存
在 async 方法里直接写 lock 会编译报错:
public async Task DoWorkAsync() {
lock (_lockObj) { // ❌ CS1996:无法在异步方法中使用 lock 语句
await Task.Delay(100);
}
}原因是 lock 依赖线程上下文连续性,而 await 可能切换线程。替代方案包括:
- 用
SemaphoreSlim(注意用await semaphore.WaitAsync()+finally { semaphore.Release(); }) - 把需要同步的纯 CPU 操作抽离到同步方法中,在
await前/后调用 - 改用不可变数据结构或无锁并发集合(如
ConcurrentQueue)
Task.Run(() => { lock (...) { ... } }) 来“绕过”,这只会增加线程开销且不解决根本问题。










