需构造高竞争临界区:用全局锁对象,线程在紧循环中频繁调用 monitor.enter 进入仅含原子操作(如计数++)的短临界区,避免i/o或sleep,以精准触发blocked状态。
想在单机上复现多线程因争抢临界区资源而集体卡死(blocked)的现场,关键不是“制造灾难”,而是精准触发监视器锁的阻塞行为——让多个线程反复、密集、无序地尝试进入同一段被 lock 或 monitor.enter 保护的代码,且不释放锁或延迟释放。
构造高竞争临界区
临界区越短、越频繁被访问,线程排队越明显;但若临界区过长(比如含 I/O 或 Sleep),反而会降低阻塞密度。理想做法是:只做极轻量操作(如原子计数++),并用 tight loop 不断重试进入。
- 定义一个全局共享对象作为锁目标,例如
private static readonly object _lockObj = new object(); - 每个线程执行循环:
for (int i = 0; i - 启动 20+ 个线程(远超 CPU 核心数),立刻观察线程状态——大量线程会在
Monitor.Enter处进入 BLOCKED 状态
故意延长锁持有时间
让一个线程长期霸占锁,其余线程只能干等。这不是 bug,而是可复现的阻塞放大器。
- 起一个“守门员线程”:获取锁后调用
Thread.Sleep(5000)或等待某个手动触发的ManualResetEvent - 其他线程在同一时刻发起
lock(_lockObj),它们不会报错,也不会跳过,而是全部挂起在入口处,状态变为 BLOCKED - 用
dotnet-dump ps或 Visual Studio 的“调试 → 窗口 → 并发可视化工具”可直观看到数十个线程堆在同一个 Monitor 上
避免自动释放干扰
lock 语句虽安全,但会自动释放锁,不利于维持 BLOCKED 队列。换成原始 Monitor 调用,能更精细控制。
- 使用
Monitor.TryEnter(obj, timeout: 0)配合失败重试,模拟“抢不到就原地卡住”的效果 - 或直接用
Monitor.Enter(obj)+ 不配对Monitor.Exit()(仅测试环境!生产禁用),让锁永不释放,后续所有线程永久 BLOCKED - 注意:.NET 中未配对
Enter/Exit可能导致SynchronizationLockException,建议在try/finally外再包一层异常捕获用于观察
验证 BLOCKED 状态是否真实发生
不能只看输出结果,要确认线程确实被操作系统/运行时挂起。
- 在 Windows 上用
perfview /threads或dotnet-trace collect --providers Microsoft-DotNETCore-Synchronization-Clrs捕获同步事件 - 检查线程堆栈:出现
Monitor.ReliableEnter、Object.Wait、Threading.Monitor.Enter等字样即为典型阻塞入口 - 用
Process Explorer查看目标进程的线程列表,State 列显示 “Wait:WrLpcReply” 或 “Wait:WrUserRequest” 也常对应 Monitor 等待










