文章目录
1、举栗子🌰
场景:公司的唯一打印机
打印机是共享资源
-
Monitor.Enter() 相当于 拿到打印权限并关门
-
Monitor.Exit() 相当于 打印完成并开门
-
Monitor.Wait() 相当于 暂时离开但保留排队位置
-
Monitor.Pulse() 相当于 通知下一个人可以准备了
2、Monitor 的本质
2.1 对象头中的秘密
每个 .NET 对象内部都有一个对象头,其中包含同步块索引:
// 概念性结构 - 实际在 CLR 中实现
class ObjectHeader
{
SyncBlockIndex syncBlock; // 同步块索引
TypeHandle typeHandle; // 类型信息
// ... 其他字段
}
同步块(SyncBlock) 才是真正的锁信息容器:
class SyncBlock
{
Thread ownerThread; // 当前拥有锁的线程
int recursionCount; // 重入计数
WaitQueue waitQueue; // 等待队列
// ... 其他同步信息
}
2.2 对象与锁的关联
object lockObj = new object(); // 任何引用类型对象都可以作为锁
// 当你执行 lock(lockObj) 时:
// 1. 检查 lockObj 的对象头中的同步块索引
// 2. 如果为0,创建新的同步块并关联
// 3. 如果已有同步块,检查锁状态
// 4. 根据情况获取锁或进入等待
3、核心工作原理
3.1 锁的获取流程
// 伪代码展示 Monitor.Enter 的内部逻辑
bool MonitorEnter(object obj, ref bool lockTaken)
{
// 第一步:快速路径 - 无竞争情况
if (obj.SyncBlockIndex == 0)
{
// 使用原子操作设置同步块
if (Interlocked.CompareExchange(ref obj.SyncBlockIndex, CreateSyncBlock(), 0) == 0)
{
obj.SyncBlock.OwnerThread = CurrentThread;
obj.SyncBlock.RecursionCount = 1;
lockTaken = true;
return true;
}
}
// 第二步:检查是否已由当前线程持有(递归锁)
if (obj.SyncBlock.OwnerThread == CurrentThread)
{
obj.SyncBlock.RecursionCount++;
lockTaken = true;
return true;
}
// 第三步:慢速路径 - 需要等待
return SlowPathMonitorEnter(obj, ref lockTaken);
}
3.2 锁的释放流程
void MonitorExit(object obj)
{
// 验证当前线程确实持有锁
if (obj.SyncBlock.OwnerThread != CurrentThread)
throw new SynchronizationLockException();
// 减少递归计数
obj.SyncBlock.RecursionCount--;
if (obj.SyncBlock.RecursionCount == 0)
{
// 真正释放锁
obj.SyncBlock.OwnerThread = null;
// 如果有等待线程,唤醒一个
if (obj.SyncBlock.WaitQueue.Count > 0)
{
WakeOneWaitingThread(obj);
}
}
}
4、Monitor 的完整使用
4.1 基础用法 - lock 语法糖
class BasicUsage
{
private object _lockObject = new object();
private int _sharedCounter = 0;
// 方式1:使用 lock 关键字(推荐)
void IncrementWithLock()
{
lock (_lockObject) // 编译器自动生成 Monitor.Enter/Exit
{
_sharedCounter++;
// 其他临界区代码
}
}
// 方式2:显式使用 Monitor(等价于上面的 lock)
void IncrementWithMonitor()
{
bool lockTaken = false;
try
{
Monitor.Enter(_lockObject, ref lockTaken);
_sharedCounter++;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_lockObject);
}
}
}
}
为什么使用 ref bool lockTaken?
// 防止异步异常导致锁无法释放
void DangerousMethod()
{
Monitor.Enter(_lockObject); // 不推荐!如果这里发生异常,锁不会被释放
try
{
// 临界区
}
finally
{
Monitor.Exit(_lockObject);
}
}
void SafeMethod()
{
bool lockTaken = false;
try
{
Monitor.Enter(_lockObject, ref lockTaken); // 原子操作,要么完全成功要么完全失败
// 临界区
}
finally
{
if (lockTaken) // 只有成功获取锁才释放
{
Monitor.Exit(_lockObject);
}
}
}
4.2 高级功能 - Wait/Pulse 机制
这是 Monitor 最强大的特性,用于复杂的线程协调:
class ProducerConsumerWithMonitor
{
private object _lockObject = new object();
private Queue<string> _queue = new Queue<string>();
private bool _isStopped = false;
// 生产者
public void Produce(string item)
{
lock (_lockObject)
{
// 如果队列太大,等待消费者处理
while (_queue.Count >= 5)
{
Console.WriteLine("生产者: 队列已满,等待...");
Monitor.Wait(_lockObject); // 释放锁并等待
}
_queue.Enqueue(item);
Console.WriteLine($"生产者: 添加 {item},队列大小: {_queue.Count}");
// 通知等待的消费者
Monitor.Pulse(_lockObject);
}
}
// 消费者
public string Consume()
{
lock (_lockObject)
{
// 如果队列为空,等待生产者
while (_queue.Count == 0 && !_isStopped)
{
Console.WriteLine("消费者: 队列为空,等待...");
Monitor.Wait(_lockObject);
}
if (_queue.Count == 0) return null; // 已停止且队列为空
string item = _queue.Dequeue();
Console.WriteLine($"消费者: 取出 {item},队列大小: {_queue.Count}");
// 通知等待的生产者
Monitor.Pulse(_lockObject);
return item;
}
}
public void Stop()
{
lock (_lockObject)
{
_isStopped = true;
Monitor.PulseAll(_lockObject); // 唤醒所有等待线程
}
}
}
// 使用示例
class Program
{
static void Main()
{
var pc = new ProducerConsumerWithMonitor();
// 启动生产者线程
Thread producer = new Thread(() => {
for (int i = 0; i < 10; i++)
{
pc.Produce($"产品-{i}");
Thread.Sleep(100);
}
});
// 启动消费者线程
Thread consumer = new Thread(() => {
for (int i = 0; i < 10; i++)
{
pc.Consume();
Thread.Sleep(150);
}
});
producer.Start();
consumer.Start();
producer.Join();
consumer.Join();
}
}
4.3 超时控制 - TryEnter
class TimeoutExample
{
private object _lockObject = new object();
public bool TryDoWork(int timeoutMs)
{
bool lockTaken = false;
try
{
// 尝试在指定时间内获取锁
lockTaken = Monitor.TryEnter(_lockObject, timeoutMs);
if (lockTaken)
{
// 成功获取锁,执行工作
DoCriticalWork();
return true;
}
else
{
// 超时,执行备用方案
Console.WriteLine($"获取锁超时 ({timeoutMs}ms),执行备用操作");
DoFallbackWork();
return false;
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(_lockObject);
}
}
}
private void DoCriticalWork()
{
Thread.Sleep(2000); // 模拟耗时工作
Console.WriteLine("关键工作完成");
}
private void DoFallbackWork()
{
Console.WriteLine("执行备用工作");
}
}
5、Wait/Pulse 的详细机制
5.1 等待队列的工作原理
class WaitPulseMechanism
{
private object _lockObject = new object();
void DemonstrateWaitPulse()
{
lock (_lockObject)
{
// 当调用 Monitor.Wait(_lockObject) 时:
// 1. 当前线程释放 _lockObject 的锁
// 2. 线程进入 _lockObject 的等待队列
// 3. 线程状态改为 WaitSleepJoin
// 当其他线程调用 Monitor.Pulse(_lockObject) 时:
// 1. 从等待队列中移出一个线程
// 2. 该线程进入就绪队列,准备重新获取锁
// 当其他线程调用 Monitor.PulseAll(_lockObject) 时:
// 1. 所有等待线程都移到就绪队列
}
}
}
5.2 复杂的协调示例
class ComplexCoordination
{
private object _lockObject = new object();
private int _currentPhase = 0;
private int _threadsReady = 0;
private const int TOTAL_THREADS = 3;
public void WorkerThread(int threadId)
{
for (int phase = 0; phase < 3; phase++)
{
lock (_lockObject)
{
// 等待进入下一阶段
while (_currentPhase != phase)
{
Monitor.Wait(_lockObject);
}
_threadsReady++;
Console.WriteLine($"线程 {threadId} 准备就绪阶段 {phase}");
// 如果所有线程都就绪,推进到下一阶段
if (_threadsReady == TOTAL_THREADS)
{
_currentPhase++;
_threadsReady = 0;
Console.WriteLine($"=== 所有线程完成阶段 {phase},进入阶段 {_currentPhase} ===");
Monitor.PulseAll(_lockObject); // 唤醒所有线程
}
else
{
// 等待其他线程
while (_currentPhase == phase)
{
Monitor.Wait(_lockObject);
}
}
}
// 执行阶段工作
Console.WriteLine($"线程 {threadId} 执行阶段 {phase} 的工作");
Thread.Sleep(500);
}
}
}
6、Monitor 的底层优化
6.1 瘦锁(Thin Lock)
对于轻度竞争的情况,CLR 使用优化:
// 概念性优化
class ThinLock
{
// 对于轻度使用,直接在对象头中存储锁信息,避免创建完整的SyncBlock
// 使用位域存储:线程ID + 递归计数
// 当竞争激烈时,才升级为完整的SyncBlock
}
6.2 自旋等待
在进入内核等待前,会先自旋一段时间:
bool TryEnterWithSpin(object obj, int timeoutMs)
{
int spinCount = CalculateOptimalSpinCount();
for (int i = 0; i < spinCount; i++)
{
if (TryEnterFastPath(obj)) return true;
Thread.SpinWait(100); // 用户态自旋,避免内核切换
}
return EnterSlowPath(obj, timeoutMs);
}
7、最佳实践和陷阱
7.1 正确做法
class BestPractices
{
private readonly object _lockObject = new object(); // readonly 防止意外修改
public void GoodPractice1()
{
lock (_lockObject)
{
// 临界区代码尽量简短
DoQuickOperation();
}
// 长时间操作放在锁外
DoLongRunningOperation();
}
public void GoodPractice2()
{
// 使用明确的超时控制
if (Monitor.TryEnter(_lockObject, 1000))
{
try
{
// 工作
}
finally
{
Monitor.Exit(_lockObject);
}
}
}
}
7.2 常见陷阱
class CommonMistakes
{
private object _lockObject = new object();
// 错误1:锁住错误的对象
public void Mistake1()
{
lock (this) // 不好!外部代码可能也锁住这个实例
{
// ...
}
}
// 错误2:在锁内调用未知方法
public void Mistake2()
{
lock (_lockObject)
{
SomeExternalMethod(); // 危险!可能造成死锁
}
}
// 错误3:忘记释放锁
public void Mistake3()
{
Monitor.Enter(_lockObject); // 如果没有对应的Exit,会导致死锁
// 应该使用 try-finally 或 lock 关键字
}
// 错误4:错误的 Wait/Pulse 使用
public void Mistake4()
{
// 必须在锁内调用 Wait/Pulse
Monitor.Wait(_lockObject); // 错误!不在锁内
}
}
8、总结
Monitor 的本质:
-
是基于对象头同步块的软件锁
-
提供递归获取能力(同一线程可多次获取)
-
内置等待/通知机制(Wait/Pulse)
-
在用户态和内核态间智能切换
核心优势:
-
与对象绑定:任何对象都可作为锁
-
递归安全:同一线程不会死锁自己
-
条件变量:内置复杂的线程协调能力
-
性能优化:瘦锁、自旋等待等优化
使用要点:
优先使用 lock 关键字
-
复杂协调使用 Wait/Pulse
-
总是用 try-finally 确保锁释放
-
避免在锁内执行耗时操作