lock是Monitor.Enter/Exit的语法糖,编译为try-finally结构确保异常时释放锁;仅支持引用类型锁对象,而Monitor支持超时、Wait/Pulse协作等高级功能。

lock 就是 Monitor.Enter + Monitor.Exit 的语法糖
直接说结论:lock(obj) { ... } 在编译后,等价于手动调用 Monitor.Enter(obj) 和 Monitor.Exit(obj),并自动包在 try-finally 块里。这意味着:你用 lock 能做到的,Monitor 全都能做;但反过来,Monitor 能做的(比如超时、等待唤醒),lock 做不到。
-
lock只支持引用类型锁对象 —— 如果传值类型(如int、struct),编译器会报错 -
Monitor.Enter理论上可传值类型,但会触发装箱,每次装箱生成新对象,导致锁失效甚至死锁,绝对不要这么做 -
lock自动确保异常下锁释放;而手写Monitor.Enter必须配try-finally,漏掉Monitor.Exit就是典型死锁源头
Monitor.Enter 的正确用法(含 C# 4.0+ 安全重载)
老式写法(易出错):
Monitor.Enter(lockObj);
try
{
// 临界区代码
}
finally
{
Monitor.Exit(lockObj);
}问题在于:如果 Monitor.Enter 本身失败(极罕见)或线程被中断,try 块可能根本没执行,但 finally 还是会跑 —— 此时调 Monitor.Exit 会抛 SynchronizationLockException。
C# 4.0 起推荐用带 ref bool 的安全重载:
bool lockTaken = false;
try
{
Monitor.Enter(lockObj, ref lockTaken);
// 临界区代码
}
finally
{
if (lockTaken)
Monitor.Exit(lockObj);
}-
lockTaken由Monitor.Enter自动设置为true仅当成功获取锁 - 即使
Enter抛异常或未进入临界区,lockTaken仍为false,Exit不会被误调 - 这是目前最健壮的手动
Monitor用法
什么时候非得用 Monitor 而不是 lock?
只有这三类场景值得放弃 lock 的简洁性,去碰 Monitor:
- 需要带超时的锁获取:用
Monitor.TryEnter(obj, timeoutMs)或TryEnter(obj, timeout, ref lockTaken),避免线程无限等待 - 要实现线程协作(如生产者-消费者):必须用
Monitor.Wait()主动释放锁并挂起,再靠Monitor.Pulse()或PulseAll()唤醒特定等待线程 - 动态控制锁粒度或嵌套逻辑:比如先尝试加锁,失败则走降级路径,而不是硬等
其他所有普通互斥场景 —— 比如保护字段、同步日志输出、更新共享集合 —— lock 更安全、更短、更不易错。
常见踩坑点:锁对象选错 or 改了
无论 lock 还是 Monitor,锁失效往往不是语法问题,而是对象语义错了:
- 锁对象不能是
public或可变字段(比如public object SyncRoot = new object();),外部代码改了它,等于换锁,同步就崩了 - 必须用
private readonly object _syncLock = new object();——readonly保证引用不变,private防止外部干扰 - 别用
this、typeof(T)、字符串字面量或装箱值类型作锁对象,它们要么暴露给外界,要么不可控地复用/新建 - 多个逻辑相关但不完全相同的资源,别共用一个锁对象(性能瓶颈);也不要把无关资源塞进同一个锁(扩大竞争面)
Monitor 本身没有魔法,它只认“对象标识”。锁对象一旦变了(哪怕只是被重新赋值),之前持有的锁就跟它再无关系 —— 这种错误不会编译报错,但会让多线程行为彻底失控。










