c# 线程安全和非线程安全的集合有什么区别

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

线程安全集合 vs 非线程安全集合:本质区别在哪

核心区别不是“能不能用”,而是“多线程同时读写时会不会崩”。

ConcurrentDictionary<string int></string>
允许多个线程同时
Add
TryGetValue
Remove
,不加锁也不抛
InvalidOperationException
;而
Dictionary<string int></string>
在同样场景下大概率崩溃——不是每次必现,但只要发生,就是数据错乱或
Collection was modified
异常。

System.Collections.Concurrent 里的集合怎么做到“不加锁也安全”

它们不用粗粒度的全局锁(比如整个

lock(_dict)
),而是靠细粒度控制 + 无锁原子操作:

ConcurrentQueue<t></t>
ConcurrentStack<t></t>
完全不用锁,靠
Interlocked.CompareExchange
等 CPU 原子指令完成入队/出栈
ConcurrentDictionary<tkey tvalue></tkey>
把内部哈希桶分段(默认 31 段),写不同段互不影响;同一段内才用轻量级
SpinLock
,避免线程挂起开销
ConcurrentBag<t></t>
为每个线程维护本地队列(
ThreadLocalList
),添加/取自己线程的数据几乎零竞争;跨线程“偷取”时才做同步

所以它不是“慢但稳”,而是“快且稳”——尤其在高并发、读写混合场景下,比手动用

lock
包裹
List<t></t>
性能高出数倍。

别误用 Synchronized 包装器:它不是线程安全的“快捷方式”

ArrayList.Synchronized
Hashtable.Synchronized
这类老式包装器,只是给每个方法加了同一个对象锁。问题很实在:

所有操作(
Add
Remove
Count
、遍历)都抢同一把锁 → 严重串行化,吞吐量暴跌
看似“线程安全”,但像
if (list.Count > 0) item = list[0];
这种两步操作,中间可能被其他线程修改 → 仍是竞态条件(race condition)
它属于
System.Collections
非泛型体系,还有装箱/拆箱开销和类型不安全问题

结论:新项目里完全不要用

Synchronized
包装器,直接上
System.Collections.Concurrent
的泛型并发集合。

什么时候该用非线程安全集合

不是“不能用”,而是“不该在共享上下文中用”。适用场景非常明确:

单线程逻辑:比如 MVC Controller 里构造一个
List<t></t>
做临时计算,返回前就丢弃
只读共享:多个线程只调用
ReadOnlyCollection<t>.AsReadOnly(list)</t>
IEnumerable<t></t>
遍历,且确保源集合创建后不再修改
局部变量 or 方法内集合:生命周期严格绑定当前线程栈,无跨线程传递 性能敏感且已用
lock
/
ReaderWriterLockSlim
手动保护的临界区(此时用
Dictionary
可能比
ConcurrentDictionary
更省内存)

最容易踩的坑是:以为“我只读不写就安全”,结果忘了某个地方悄悄调了

ToList()
ToArray()
,返回的是新集合——但原始集合若还在被其他线程写,那这个“只读快照”本身就不一致。

真正要记住的不是“哪个类安全”,而是“谁在访问、怎么访问、生命周期归谁管”。

ConcurrentBag
不是万能解药,
List<t></t>
也不是洪水猛兽——关键在上下文。

相关推荐