c# 如何在高并发下安全地操作静态字典或列表

来源:这里教程网 时间:2026-02-21 17:37:17 作者:

为什么直接用
static Dictionary<k v></k>
会出问题

因为

Dictionary<k v></k>
List<t></t>
都不是线程安全的。高并发下多个线程同时调用
Add
Remove
或遍历,可能触发
InvalidOperationException
(“集合已修改”),甚至出现数据丢失、索引越界或内存损坏——这不是偶发 bug,而是确定性崩溃。

ConcurrentDictionary<k v></k>
替代静态
Dictionary

这是最直接的替换方案,它内部用分段锁 + CAS 操作保证读写安全,且读操作无锁,性能远好于全局

lock

GetOrAdd(key, factory)
是原子的,适合缓存场景,避免重复初始化
AddOrUpdate(key, addValueFactory, updateValueFactory)
可安全处理“有则更新、无则添加”逻辑
遍历时用
foreach
是安全的,但迭代器看到的是快照,不反映实时增删
避免用
Keys
Values
属性后直接遍历——它们返回的是只读集合,但底层仍可能被其他线程修改,建议先转成数组:
var keys = dict.Keys.ToArray();

静态
List<t></t>
的安全替代:优先用
ConcurrentQueue<t></t>
ConcurrentBag<t></t>

如果只是追加+消费(如日志缓冲、任务队列),别硬扛

List<t></t>
+
lock
。静态
List<t></t>
几乎没有安全又高效的并发使用方式。

ConcurrentQueue<t></t>
:FIFO,
Enqueue
/
TryDequeue
全部线程安全,无锁实现,适合生产者-消费者模型
ConcurrentBag<t></t>
:无序,对“同一线程频繁 Add/Remove”做了本地缓存优化,适合短期对象池场景
真需要随机访问索引?考虑
ImmutableList<t></t>
+ 原子更新:
list = list.Add(item); // 返回新实例,旧引用不变
但注意内存开销和 GC 压力

万不得已用
lock
时,必须避开常见陷阱

静态锁对象必须是私有、只读、不可变的,且不能是

typeof(MyClass)
或字符串字面量——它们可能被外部代码锁定,引发死锁或锁竞争放大。

正确写法:
private static readonly object _lockObj = new object();
锁粒度要细:不要把整个方法体包进
lock
,只锁真正共享修改的几行
禁止在锁内调用未知外部代码(如事件、虚方法、LINQ 查询),可能反向调用本类导致死锁 避免嵌套锁:同一上下文多次获取同一锁对象不会死锁,但若涉及多个锁,顺序不一致就极易死锁 静态集合在高并发下的“安全”,本质是选对类型而非修修补补。
ConcurrentDictionary
ConcurrentQueue
已覆盖绝大多数场景;手动加锁是最后手段,且一旦引入,就必须严格控制锁对象生命周期和临界区范围。

相关推荐