应使用ConcurrentDictionary替代static Dictionary,因其采用分段锁与CAS保证线程安全;静态List则优先选用ConcurrentQueue或ConcurrentBag,避免手动加锁引发死锁或性能问题。

为什么直接用 static Dictionary 会出问题
因为 Dictionary 和 List 都不是线程安全的。高并发下多个线程同时调用 Add、Remove 或遍历,可能触发 InvalidOperationException(“集合已修改”),甚至出现数据丢失、索引越界或内存损坏——这不是偶发 bug,而是确定性崩溃。
用 ConcurrentDictionary 替代静态 Dictionary
这是最直接的替换方案,它内部用分段锁 + CAS 操作保证读写安全,且读操作无锁,性能远好于全局 lock。
-
GetOrAdd(key, factory)是原子的,适合缓存场景,避免重复初始化 -
AddOrUpdate(key, addValueFactory, updateValueFactory)可安全处理“有则更新、无则添加”逻辑 - 遍历时用
foreach是安全的,但迭代器看到的是快照,不反映实时增删 - 避免用
Keys或Values属性后直接遍历——它们返回的是只读集合,但底层仍可能被其他线程修改,建议先转成数组:var keys = dict.Keys.ToArray();
静态 List 的安全替代:优先用 ConcurrentQueue 或 ConcurrentBag
如果只是追加+消费(如日志缓冲、任务队列),别硬扛 List + lock。静态 List 几乎没有安全又高效的并发使用方式。
-
ConcurrentQueue:FIFO,Enqueue/TryDequeue全部线程安全,无锁实现,适合生产者-消费者模型 -
ConcurrentBag:无序,对“同一线程频繁 Add/Remove”做了本地缓存优化,适合短期对象池场景 - 真需要随机访问索引?考虑
ImmutableList+ 原子更新:list = list.Add(item); // 返回新实例,旧引用不变
但注意内存开销和 GC 压力
万不得已用 lock 时,必须避开常见陷阱
静态锁对象必须是私有、只读、不可变的,且不能是 typeof(MyClass) 或字符串字面量——它们可能被外部代码锁定,引发死锁或锁竞争放大。
- 正确写法:
private static readonly object _lockObj = new object();
- 锁粒度要细:不要把整个方法体包进
lock,只锁真正共享修改的几行 - 禁止在锁内调用未知外部代码(如事件、虚方法、LINQ 查询),可能反向调用本类导致死锁
- 避免嵌套锁:同一上下文多次获取同一锁对象不会死锁,但若涉及多个锁,顺序不一致就极易死锁
ConcurrentDictionary 和 ConcurrentQueue 已覆盖绝大多数场景;手动加锁是最后手段,且一旦引入,就必须严格控制锁对象生命周期和临界区范围。










