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