—— 掌握Monitor类与线程异常处理的关键技巧
🔒 一、使用Monitor类避免死锁(代码解析)
1. 死锁场景复现
// 错误示例:使用lock导致死锁
lock(lock2) {
Thread.Sleep(1000);
lock(lock1) { // 等待lock1释放(但已被其他线程占用)
Console.WriteLine("Acquired resource");
}
}
-
死锁条件: -
线程A持有 lock1,等待lock2 -
主线程持有 lock2,等待lock1 -
双方互相等待 → 程序永久挂起
2. Monitor.TryEnter 解决方案
if(Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) {
Console.WriteLine("Acquired resource successfully");
} else {
Console.WriteLine("Timeout acquiring resource!"); // 关键逃生通道
}
-
核心优势: -
⏱️ 超时机制:尝试获取锁最多5秒,超时返回 false -
💡 避免永久阻塞:线程不会无限等待,可执行备用逻辑 -
🔄 资源释放保障:配合 finally块确保锁安全释放
3. lock关键字的本质
// lock 的等价实现
bool acquiredLock = false;
try {
Monitor.Enter(lockObject, ref acquiredLock);
// ... 临界区代码 ...
} finally {
if (acquiredLock) Monitor.Exit(lockObject);
}
📌 关键结论:
lock是Monitor.Enter的语法糖,但缺少超时控制能力。高并发场景中,优先使用Monitor.TryEnter可显著提升系统健壮性。
⚠️ 二、线程异常处理的核心原则
1. 线程内异常必须内部捕获
// 正确做法:线程内部try/catch
static void FaultyThread() {
try {
throw new Exception("Boom!");
} catch (Exception ex) {
Console.WriteLine($"Handled: {ex.Message}"); // 异常被成功捕获
}
}
2. 主线程无法捕获子线程异常(经典误区)
try {
var t = new Thread(BadFaultyThread);
t.Start(); // 子线程抛出异常
} catch (Exception ex) {
// 此处永远无法捕获子线程异常!
Console.WriteLine("We won't get here!");
}
-
根本原因:
线程是独立执行单元,异常不会冒泡到创建它的线程。
3. 未处理线程异常的灾难性后果
-
程序直接崩溃(.NET Framework中进程终止) -
资源泄露(文件句柄/网络连接未释放) -
日志丢失(异常信息未被记录)
🛡️ 三、实战最佳实践总结
|
|
|
|
|---|---|---|
|
|
lock
|
Monitor.TryEnter
|
|
|
|
|
|
|
|
finally
|
补充建议:
-
锁顺序标准化:所有线程按固定顺序获取锁(如先 lock1后lock2) -
全局异常处理:注册 AppDomain.CurrentDomain.UnhandledException兜底 -
异步编程替代:推荐使用 Task+async/await(内置聚合异常机制)
💎 终极原则:
"线程即独立沙箱" —— 所有资源操作与异常必须在内部闭环处理!
延伸思考:在分布式系统中,死锁问题会演变为更复杂的分布式死锁,此时需引入超时回滚与死锁检测算法。而异常处理则需要跨服务日志聚合(如ELK栈)实现全局追踪 —— 多线程是构建高并发系统的基石,掌握其核心陷阱方能设计出弹性架构。

