c# 锁升级是什么 c# lock的实现原理

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

lock 是 Monitor 的语法糖,没有“锁升级”概念

直接说结论:

lock
关键字在 C# 中**不支持也不实现 JVM 那种偏向锁 → 轻量级锁 → 重量级锁的锁升级机制**。它底层始终调用
Monitor.Enter
Monitor.Exit
,走的是 .NET 运行时统一的同步块(SyncBlock)路径——一旦争用发生,就可能触发内核态等待(如
CRITICAL_SECTION
或事件对象),不存在“先尝试轻量自旋、失败再升级”的分阶段策略。

lock 的真实实现:SyncBlock + 线程 ID 绑定

每个被

lock
的引用类型对象(
object
class
实例等)在堆上分配时,会预留一个
SyncBlockValue
字段(32 位)。这个字段前 6 位用于标记状态(比如第 5 位是“已锁定”标志),后 26 位存储指向全局
SyncTable
中某个
SyncBlock
条目的索引。

当线程 A 执行

lock(_obj)
时:

检查
_obj.SyncBlockValue
第 5 位是否为 0;是,则尝试从
g_pSyncTable
分配一个新
SyncBlock
,填入当前线程 ID,并把索引写回后 26 位
若第 5 位已被置 1,说明已被占用,就去查对应
SyncBlock
里存的线程 ID —— 如果不是自己,就挂起等待(进入内核态)
释放锁时,
Monitor.Exit
清除该
SyncBlock
的持有者,并可能复用或回收条目

注意:

string
类型禁止作为
lock
对象(字符串驻留导致意外共享),值类型会触发装箱(每次新建对象,锁失效),
null
直接抛
NullReferenceException

别把 .NET 的 SpinLock / ReaderWriterLockSlim 当成 lock 升级

有人看到

SpinLock
ReaderWriterLockSlim
就联想“这是 lock 的升级版”,这是误解。它们是**完全独立的同步原语**:

SpinLock
:纯用户态自旋(无上下文切换),适合极短临界区且低争用场景;但它是结构体,不能用于
lock
语句,必须手动
Enter
/
Exit
ReaderWriterLockSlim
:支持读多写少的细粒度并发,内部有状态机和队列,和
Monitor
无继承/升级关系
lock
永远是排他、不可重入、基于对象头的简单互斥 —— 它没“读写分离”,也没“尝试获取超时”能力

想替代

lock
?选对工具就行,但别指望编译器或运行时自动帮你“升级”它。

容易踩的坑:看似安全,实则锁失效

最常见错误不是不会用

lock
,而是锁的对象不对:

private readonly object _lock = new object();
✅ 安全 —— 私有、只读、静态(如果跨实例共享)才真正唯一
lock(this)
❌ 危险 —— 外部可拿到
this
并也去
lock
,造成死锁或意外同步
lock("mylock")
❌ 错误 —— 字符串字面量被驻留,所有地方用同一字符串字面量都会锁住同一个对象
lock(someProperty)
❌ 不稳 —— 属性 getter 可能返回新对象,每次
lock
实际锁不同实例

另外,

lock
块里别做耗时操作(I/O、网络、长循环),否则阻塞其他线程,且无法取消 —— 这时候该换
SemaphoreSlim.WaitAsync()
或专用异步锁。

归根结底,

lock
就是一把朴素但可靠的门栓:它不聪明,不自适应,也不升级;你给它一个好门(正确对象),它就守好;你给它一扇破窗(
string
或可变属性),它就形同虚设。

相关推荐

热文推荐