c# Monitor 和 lock 有什么区别 c# Monitor.Enter怎么用

6次阅读

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

c# Monitor 和 lock 有什么区别 c# Monitor.Enter怎么用

lock 就是 Monitor.Enter + Monitor.Exit 的语法糖

直接说结论:lock(obj) { ... } 在编译后,等价于手动调用 Monitor.Enter(obj)Monitor.Exit(obj),并自动包在 try-finally 块里。这意味着:你用 lock 能做到的,Monitor 全都能做;但反过来,Monitor 能做的(比如超时、等待唤醒),lock 做不到。

  • lock 只支持引用类型锁对象 —— 如果传值类型(如 intStruct),编译器会报错
  • 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); }
  • lockTakenMonitor.Enter 自动设置为 true 仅当成功获取锁
  • 即使 Enter 抛异常或未进入临界区,lockTaken 仍为 falseExit 不会被误调
  • 这是目前最健壮的手动 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 防止外部干扰
  • 别用 thistypeof(T)字符串字面量或装箱值类型作锁对象,它们要么暴露给外界,要么不可控地复用/新建
  • 多个逻辑相关但不完全相同的资源,别共用一个锁对象(性能瓶颈);也不要把无关资源塞进同一个锁(扩大竞争面)

Monitor 本身没有魔法,它只认“对象标识”。锁对象一旦变了(哪怕只是被重新赋值),之前持有的锁就跟它再无关系 —— 这种错误不会编译报错,但会让多线程行为彻底失控。

text=ZqhQzanResources