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

来源:这里教程网 时间:2026-02-21 17:39:48 作者:

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

相关推荐