为什么 lock(typeof(MyClass))
会锁住整个类型而不是实例
因为
typeof(MyClass)返回的是运行时的
Type对象,而每个类型在 AppDomain(或 .NET Core/.NET 5+ 的 AssemblyLoadContext)中只有一个全局唯一的
Type实例。所有对该类型的
lock(typeof(MyClass))调用,本质上都在争抢同一把锁 —— 即使发生在完全无关的类、模块甚至第三方库中。
它和 lock(this)
或 private static readonly object _lock = new()
的关键区别
前者是**跨类、跨模块、跨调用栈的隐式全局锁**;后两者作用域明确、可控。常见后果包括:
不同业务逻辑(比如日志初始化和配置加载)意外串行,导致本可并发的操作被阻塞 第三方 NuGet 包若也用了lock(typeof(MyClass))(尤其在通用工具类里),你的代码可能被它拖慢,反之亦然 在 ASP.NET Core 等多租户场景下,一个请求卡住
lock(typeof(Startup)),可能让所有后续请求排队等待 单元测试并行执行时,多个测试用例因共享同一
Type锁而相互干扰,出现偶发超时或死锁
typeof(MyClass)
锁在 .NET Core / .NET 5+ 中是否更危险
是的。.NET Core 引入了
AssemblyLoadContext,同一个程序集可能被加载多次(例如插件场景、动态编译)。此时
typeof(MyClass)在不同上下文中返回的
Type对象**不相等**,但开发者通常意识不到这点 —— 表面看是“同一个类”,实际锁根本不同,导致本该串行的逻辑变成竞态;或者反过来,误以为安全而没加锁,结果出错。
更隐蔽的问题是:IL 编译器或 AOT(如 NativeAOT)可能对
typeof做优化,进一步加剧行为不一致。
替代方案:安全又清晰的写法
用显式声明的静态锁对象,确保作用域唯一、意图明确:
public class MyClass
{
private static readonly object _syncRoot = new();
<pre class='brush:php;toolbar:false;'>public void DoWork()
{
lock (_syncRoot)
{
// 安全的临界区
}
}}
如果需要细粒度控制(如按 ID 锁不同资源),改用
ConcurrentDictionary<string object></string>+
GetOrAdd,避免锁爆炸:
private static readonly ConcurrentDictionary<string, object> _perKeyLocks
= new();
<p>public void DoWorkFor(string key)
{
var lockObj = <em>perKeyLocks.GetOrAdd(key, </em> => new object());
lock (lockObj)
{
// 按 key 隔离,不干扰其他 key
}
}绝对不要依赖
Type、
string字面量(如
lock("MyClass"))、或任何可能被外部代码复用的引用对象作锁目标。
真正麻烦的从来不是“要不要加锁”,而是“锁的边界是否恰好覆盖你要保护的资源,且不超出”。
typeof(MyClass)的边界是不可控的全局,这在现代分层、插件化、测试并行的 C# 工程里,基本等于埋雷。
