lock(typeof(MyClass))会锁住整个类型而非实例,因为每个类型在AppDomain或AssemblyLoadContext中仅有一个全局唯一的Type实例,所有对该类型的lock调用均争抢同一把锁,形成跨类、跨模块的隐式全局锁,易引发意外阻塞、第三方干扰及多上下文竞态等问题。

为什么 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();
public void DoWork()
{
lock (_syncRoot)
{
// 安全的临界区
}
}}
如果需要细粒度控制(如按 ID 锁不同资源),改用 ConcurrentDictionary + GetOrAdd,避免锁爆炸:
private static readonly ConcurrentDictionary _perKeyLocks
= new();
public void DoWorkFor(string key)
{
var lockObj = perKeyLocks.GetOrAdd(key, => new object());
lock (lockObj)
{
// 按 key 隔离,不干扰其他 key
}
}
绝对不要依赖 Type、string 字面量(如 lock("MyClass"))、或任何可能被外部代码复用的引用对象作锁目标。
真正麻烦的从来不是“要不要加锁”,而是“锁的边界是否恰好覆盖你要保护的资源,且不超出”。typeof(MyClass) 的边界是不可控的全局,这在现代分层、插件化、测试并行的 C# 工程里,基本等于埋雷。










