lock是Monitor.Enter/Exit的语法糖,编译为try-finally结构确保异常时释放锁;仅支持引用类型锁对象,而Monitor支持超时、Wait/Pulse协作等高级功能。
直接说结论: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(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(lock
Obj);
}lockTaken 由 Monitor.Enter 自动设置为 true 仅当成功获取锁Enter 抛异常或未进入临界区,lockTaken 仍为 false,Exit 不会被误调Monitor 用法只有这三类场景值得放弃 lock 的简洁性,去碰 Monitor:
Monitor.TryEnter(obj, timeoutMs) 或 TryEnter(obj, timeout, ref lockTaken),避免线程无限等待Monitor.Wait() 主动释放锁并挂起,再靠 Monitor.Pulse() 或 PulseAll() 唤醒特定等待线程其他所有普通互斥场景 —— 比如保护字段、同步日志输出、更新共享集合 —— lock 更安全、更短、更不易错。
无论 lock 还是 Monitor,锁失效往往不是语法问题,而是对象语义错了:
public 或可变字段(比如 public object SyncRoot = new object();),外部代码改了它,等于换锁,同步就崩了private readonly object _syncLock = new object(); —— readonly 保证引用不变,private 防止外部干扰this、typeof(T)、字符串字面量或装箱值类型作锁对象,它们要么暴露给外界,要么不可控地复用/新建Monitor 本身没有魔法,它只认“对象标识”。锁对象一旦变了(哪怕只是被重新赋值),之前持有的锁就跟它再无关系 —— 这种错误不会编译报错,但会让多线程行为彻底失控。