lock是Monitor的语法糖,编译后完全等价于Monitor.Enter+try-finally+Monitor.Exit;它自动确保异常时释放锁,但不支持超时、Wait/Pulse等高级功能。

lock 就是 Monitor 的语法糖,别把它当两个东西学
你写 lock(obj) { ... },编译器立刻把它翻译成 Monitor.Enter + try-finally + Monitor.Exit。不是“类似”,是**完全等价**——IL 层面一模一样。所以别纠结“哪个更底层”,lock 不是替代品,它是 Monitor 的安全封装。
- 编译后自动加
try-finally,哪怕临界区抛异常,锁也必释放(不用你操心) - 不支持超时、等待、唤醒——这些功能根本没暴露出来
- 只接受引用类型作为锁对象;传值类型会隐式装箱,每次装箱生成新对象,导致锁失效甚至死锁
Monitor.TryEnter 能帮你躲开死锁,lock 做不到
当你不确定锁会不会被长时间占用(比如依赖外部服务、数据库慢查询、或另一个可能卡住的线程),用 Monitor.TryEnter(obj, timeout) 是唯一靠谱的选择。它返回 bool:成功拿到锁就干正事,失败就走降级逻辑,而不是傻等。
-
timeout单位是毫秒,传0表示“试试看,不等”;传-1等价于无限等待(和Monitor.Enter一样) - 必须配对调用
Monitor.Exit,且只能在lockTaken == true时调用,否则抛SynchronizationLockException - 常见错误:忘了在
finally里检查lockTaken,直接Monitor.Exit→ 运行时报错
bool lockTaken = false;
try
{
if (Monitor.TryEnter(_syncObj, 500)) // 等最多 500ms
{
lockTaken = true;
// 执行临界区
}
else
{
Log.Warn("获取锁超时,跳过处理");
return;
}
}
finally
{
if (lockTaken) Monitor.Exit(_syncObj);
}
Wait/Pulse 只能在 Monitor 里用,lock 完全没这能力
如果你在写生产者-消费者、信号量控制、状态驱动的协同逻辑(比如“等数据来了再处理”),lock 直接出局。Monitor.Wait 会**主动释放锁并挂起当前线程**,直到另一个线程调用 Monitor.Pulse 或 Monitor.PulseAll 唤醒它——这是协作式同步的核心机制。
-
Wait必须在已持有锁的前提下调用,否则抛SynchronizationLockException -
Pulse只唤醒一个等待线程;PulseAll唤醒全部——但唤醒不等于立即执行,它们还得重新竞争锁 - 典型陷阱:先
Pulse后Wait(即“信号丢失”),必须用循环条件检查 +while包裹Wait
锁对象选错,lock 和 Monitor 都会翻车
无论用哪个,锁对象本身出问题,同步就形同虚设。最常见三类雷:
- 锁
this:外部代码可能也锁你实例,引发意外阻塞 - 锁
typeof(MyClass)或字符串字面量:跨 Assembly 或 intern 字符串共享,锁范围远超预期 - 锁 public 字段或可变对象(比如
public object SyncRoot):别人改了它,你的Monitor.Exit就找不到原锁对象
正确做法永远是:private readonly object _syncObj = new object(); —— 它只属于你,不可变,不暴露。
Monitor 提供能力,lock 提供安全底线;该用哪个,不看“高级感”,而看有没有 Wait/Pulse 或超时需求。其余时候,老老实实写 lock,少一行代码,少一个 bug。










