在C#多线程编程场景中,普通的Dictionary类型不具备线程安全特性,当多个线程同时对同一个Dictionary实例进行读写操作时,很容易出现键不存在异常、数据覆盖、内部状态损坏等问题。ConcurrentDictionary是System.Collections.Concurrent命名空间下提供的线程安全字典实现,从设计层面解决了多线程操作字典的冲突问题,无需开发者手动编写额外的锁逻辑即可安全使用。

ConcurrentDictionary基础用法
初始化与添加元素
ConcurrentDictionary的初始化和普通字典类似,支持指定初始容量和键值对比较器,添加元素时可以使用AddOrUpdate、TryAdd等方法,避免直接赋值带来的冲突问题。
using System.Collections.Concurrent;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// 初始化ConcurrentDictionary
ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
// 多线程添加元素
Parallel.For(0, 10, i =>
{
string key = $"key_{i}";
// TryAdd尝试添加键值对,添加成功返回true,键已存在返回false,不会抛异常
bool addResult = concurrentDict.TryAdd(key, i);
System.Console.WriteLine($"添加{key},结果:{addResult}");
});
}
}
获取与更新元素
获取元素时可以使用TryGetValue方法避免键不存在抛异常,更新元素时推荐使用AddOrUpdate方法,它内部会处理并发更新的冲突,保证更新操作的原子性。
using System.Collections.Concurrent;
class Program
{
static void Main()
{
ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
concurrentDict.TryAdd("test_key", 0);
// 尝试获取元素
if (concurrentDict.TryGetValue("test_key", out int value))
{
System.Console.WriteLine($"获取到值:{value}");
}
// 更新元素,如果键不存在则添加,存在则更新为旧值+1
int newValue = concurrentDict.AddOrUpdate(
"test_key",
1, // 键不存在时的初始值
(key, oldVal) => oldVal + 1 // 键存在时的更新逻辑
);
System.Console.WriteLine($"更新后的值:{newValue}");
}
}
删除元素
删除元素使用TryRemove方法,它会原子性地判断键是否存在并删除,避免多线程下判断存在后还没删除就被其他线程修改的问题。
using System.Collections.Concurrent;
class Program
{
static void Main()
{
ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
concurrentDict.TryAdd("del_key", 100);
// 尝试删除元素,返回是否删除成功以及被删除的值
if (concurrentDict.TryRemove("del_key", out int removedValue))
{
System.Console.WriteLine($"删除成功,被删除的值:{removedValue}");
}
}
}
ConcurrentDictionary如何处理多线程冲突
内部锁机制设计
ConcurrentDictionary没有使用全局锁控制所有操作,而是采用了分段锁(细粒度锁)的设计:内部将字典的存储分成多个小的分区,每个分区有独立的锁。当进行写操作(添加、更新、删除)时,只会锁住对应的分区,其他分区的操作可以正常执行,大大降低了锁的冲突概率。
读操作在无竞争的情况下是无锁的,多个线程可以同时读取不同分区甚至同一分区的数据,不需要获取锁,因此读性能远高于普通字典加全局锁的方案。
原子性操作保证
ConcurrentDictionary的所有公开方法都是原子性的,比如TryAdd、AddOrUpdate、TryRemove等方法,在执行过程中不会被其他线程打断,不会出现两个线程同时添加同一个键导致数据覆盖或者异常的情况。以AddOrUpdate为例,它的更新逻辑是在内部锁的保护下执行的,保证同一个键的更新操作串行执行。
冲突处理示例
下面模拟多个线程同时更新同一个键的场景,观察ConcurrentDictionary的处理效果:
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Linq;
class Program
{
static void Main()
{
ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
string targetKey = "counter";
concurrentDict.TryAdd(targetKey, 0);
// 10个线程同时更新同一个键
Parallel.For(0, 10, i =>
{
concurrentDict.AddOrUpdate(targetKey, 1, (k, v) => v + 1);
});
// 最终结果应该是10,不会出现小于10的情况
System.Console.WriteLine($"最终计数:{concurrentDict[targetKey]}");
}
}
与普通字典加锁方案对比
很多开发者会在普通Dictionary外面包一层全局锁来处理多线程问题,这种方案和ConcurrentDictionary的差异如下:
| 对比项 | 普通Dictionary+全局锁 | ConcurrentDictionary |
|---|---|---|
| 线程安全实现方式 | 手动加全局锁,所有操作串行执行 | 内部细粒度锁+无锁读,写操作按分区加锁 |
| 读性能 | 读操作也需要获取锁,性能低 | 无锁读,多读场景性能高 |
| 写冲突处理 | 所有写操作互斥,冲突概率高 | 仅同分区写操作互斥,冲突概率低 |
| 使用复杂度 | 需要手动处理锁逻辑,容易遗漏导致问题 | 直接调用封装好的方法,无需额外锁逻辑 |
使用注意事项
- 不要直接调用
concurrentDict[key] = value的方式赋值,这种方式不是原子操作,多线程下可能出现问题,推荐使用AddOrUpdate方法。 - 如果需要遍历ConcurrentDictionary,遍历过程中如果有其他线程修改字典,不会抛异常,但遍历到的数据可能是修改前的快照,不是实时的。
- ConcurrentDictionary的键必须保证不可变,且正确实现GetHashCode和Equals方法,否则可能出现键匹配错误的问题。
- 如果业务场景是单线程操作字典,不需要使用ConcurrentDictionary,普通Dictionary的性能会更高。
C#ConcurrentDictionary多线程字典冲突线程安全字典并发集合修改时间:2026-06-24 14:54:32