在C#中处理多线程同步时,我们经常会看到lock关键字,也会听到Monitor类。很多初学者以为这是两套不同的机制,其实它们关系非常紧密:lock语句本质上是Monitor类的语法简化形式。

一、lock语句的底层展开
当我们写下lock(obj) { ... }时,C#编译器并不会生成某种特殊的运行时指令,而是将其翻译成对Monitor类的调用。早期版本大致等价于下面的代码:
object obj = new object();
Monitor.Enter(obj);
try
{
// 被保护的代码
}
finally
{
Monitor.Exit(obj);
}
在较新的C#版本中,为了避免极少见的线程竞争问题,编译器会使用Monitor.Enter的重载方法并在try中记录是否成功获取锁,但核心思想不变:lock就是Monitor.Enter加Monitor.Exit的封装。
二、为什么要有lock语句
直接使用Monitor类要求开发者手动写try finally,一旦忘记在finally中调用Monitor.Exit,就可能导致死锁。lock语句通过语法层面的约束,强制编译器生成正确的释放逻辑,降低了出错概率。
简单对比
| 方式 | 写法复杂度 | 安全性 |
|---|---|---|
| lock语句 | 低 | 高,自动释放 |
| Monitor类 | 高,需手动try finally | 依赖开发者写法 |
三、Monitor类提供的额外能力
因为lock只是基础封装,当我们需要更细粒度的控制时,就要直接使用Monitor类。例如设置获取锁的超时时间:
object lockObj = new object();
bool lockTaken = false;
try
{
Monitor.Enter(lockObj, ref lockTaken);
// 临界区代码
}
finally
{
if (lockTaken)
{
Monitor.Exit(lockObj);
}
}
此外,Monitor还支持Monitor.Wait和Monitor.Pulse等方法,用于线程间的等待与通知,这些是lock语句无法直接表达的。
四、常见使用误区
- 不要对值类型使用lock,因为值类型会被装箱,每次加锁的对象不同,起不到同步作用。
- 不要使用this、typeof(类)或字符串作为锁对象,容易造成外部代码意外干扰。
- lock内部不要调用可能阻塞很久的外部方法,否则会长时间占用锁。
五、总结
简单来说,C#的lock语句是Monitor类在语法层面的友好封装,编译后依赖于Monitor.Enter与Monitor.Exit实现互斥。日常开发中优先使用lock来保证基本线程安全;当需要超时、条件等待等高级功能时,再直接使用Monitor类的相关方法即可。