C# ConcurrentDictionary的使用小结
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>✅ 一、为什么需要ConcurrentDictionary?</li><li>🧱 二、核心特性</li><li>🔧 三、常用 API 与示例</li><ul class="second_class_ul"><li>1. 创建</li><li>2. 基本操作(线程安全)</li><li>3. 高级原子操作(⭐ 最常用!)</li><ul class="third_class_ul"><li>✅GetOrAdd(key, valueFactory)</li><li>✅AddOrUpdate(key, addValueFactory, updateValueFactory)</li></ul></ul><li>⚖️ 四、与加锁Dictionary的性能对比</li><ul class="second_class_ul"></ul><li>🚫 五、常见误区</li><ul class="second_class_ul"><li>❌ 误区 1:认为dict = value是原子的</li><ul class="third_class_ul"></ul><li>❌ 误区 2:在GetOrAdd中做非幂等操作</li><ul class="third_class_ul"></ul></ul><li>🛠 六、典型应用场景</li><ul class="second_class_ul"><li>1.内存缓存(Cache)</li><ul class="third_class_ul"></ul><li>2.计数器 / 统计</li><ul class="third_class_ul"></ul><li>3.对象池(Object Pool)</li><ul class="third_class_ul"></ul></ul><li>📏 七、性能调优建议</li><ul class="second_class_ul"></ul><li>✅ 总结:何时使用ConcurrentDictionary?</li><ul class="second_class_ul"></ul></ul></div><p><code>ConcurrentDictionary<TKey, TValue></code> 是 .NET 中一个<strong>线程安全的字典集合</strong>,专为<strong>高并发读写场景</strong>设计。它是 <code>System.Collections.Concurrent</code> 命名空间下的核心类型之一,适用于多线程环境中需要高效、安全地共享键值对数据的场景。</p><p class="maodian"></p><h2>✅ 一、为什么需要ConcurrentDictionary?</h2>
<p>普通 <code>Dictionary<TKey, TValue></code> <strong>不是线程安全的</strong>。如果多个线程同时读写:</p>
<div class="jb51code"><pre class="brush:csharp;">var dict = new Dictionary<string, int>();
// 线程A: dict["a"] = 1;
// 线程B: dict["b"] = 2;
// 可能抛出 InvalidOperationException 或数据损坏!
</pre></div>
<p>即使加锁(<code>lock</code>)也能实现线程安全,但会带来性能瓶颈(串行化访问)。</p>
<p>而 <code>ConcurrentDictionary</code>:</p>
<ul><li><strong>无需外部加锁</strong></li><li><strong>内部使用细粒度锁或无锁算法</strong></li><li><strong>支持高并发读 + 适度并发写</strong></li></ul>
<p class="maodian"></p><h2>🧱 二、核心特性</h2>
<table><thead><tr><th>特性</th><th>说明</th></tr></thead><tbody><tr><td><strong>线程安全</strong></td><td>所有公共成员(Add、Get、Remove 等)都是线程安全的</td></tr><tr><td><strong>高性能并发读</strong></td><td>读操作几乎无锁(lock-free),性能接近普通字典</td></tr><tr><td><strong>分段/桶式结构</strong></td><td>内部将数据分片(buckets),减少写冲突</td></tr><tr><td><strong>原子操作支持</strong></td><td>提供 <code>AddOrUpdate</code>, <code>GetOrAdd</code> 等复合原子操作</td></tr><tr><td><strong>不保证顺序</strong></td><td>和 <code>Dictionary</code> 一样,不维护插入顺序</td></tr></tbody></table>
<blockquote><p>⚠️ 注意:ConcurrentDictionary 的枚举(foreach)是线程安全的快照,但可能包含“过时”数据(因为其他线程可能正在修改)。</p></blockquote>
<p class="maodian"></p><h2>🔧 三、常用 API 与示例</h2>
<p class="maodian"></p><h3>1. 创建</h3>
<div class="jb51code"><pre class="brush:csharp;">var cache = new ConcurrentDictionary<string, int>();
// 或指定初始容量和并发级别(高级用法)
var cache2 = new ConcurrentDictionary<string, int>(concurrencyLevel: 4, capacity: 16);
</pre></div>
<p class="maodian"></p><h3>2. 基本操作(线程安全)</h3>
<div class="jb51code"><pre class="brush:csharp;">// 添加(如果不存在)
cache.TryAdd("key1", 100); // 返回 bool
// 获取(如果存在)
if (cache.TryGetValue("key1", out int value))
{
Console.WriteLine(value); // 100
}
// 更新(如果存在)
cache.TryUpdate("key1", 200, 100); // 仅当当前值为100时更新为200
// 删除
cache.TryRemove("key1", out int removedValue);
</pre></div>
<p class="maodian"></p><h3>3. 高级原子操作(⭐ 最常用!)</h3>
<p class="maodian"></p><h4>✅GetOrAdd(key, valueFactory)</h4>
<blockquote><p>如果 key 不存在,则调用工厂方法创建值并添加;否则返回现有值。</p></blockquote>
<div class="jb51code"><pre class="brush:csharp;">var config = cache.GetOrAdd("config", key =>
{
// 模拟耗时加载(只执行一次!)
Thread.Sleep(1000);
return LoadConfigFromDatabase();
});
</pre></div>
<blockquote><p>💡 多个线程同时调用 GetOrAdd("config", ...) 时,工厂方法只会<strong>被调用一次</strong>(其他线程等待结果),避免重复初始化!</p></blockquote>
<p class="maodian"></p><h4>✅AddOrUpdate(key, addValueFactory, updateValueFactory)</h4>
<blockquote><p>如果不存在则添加,存在则更新。</p></blockquote>
<div class="jb51code"><pre class="brush:csharp;">// 实现计数器
cache.AddOrUpdate("counter",
addValue: 1, // 不存在时设为1
updateValueFactory: (key, oldValue) => oldValue + 1 // 存在时+1
);
</pre></div>
<p class="maodian"></p><h2>⚖️ 四、与加锁Dictionary的性能对比</h2>
<table><thead><tr><th>场景</th><th>Dictionary + lock</th><th>ConcurrentDictionary</th></tr></thead><tbody><tr><td>高并发读</td><td>所有读需排队(慢)</td><td>几乎无锁(快)</td></tr><tr><td>低并发写</td><td>串行写(中等)</td><td>分段锁(较快)</td></tr><tr><td>高并发写</td><td>严重瓶颈</td><td>仍优于全局锁</td></tr><tr><td>代码简洁性</td><td>需手动管理锁</td><td>无需锁,API 更丰富</td></tr></tbody></table>
<blockquote><p>📊 在典型 Web 应用缓存场景(大量读 + 少量写),ConcurrentDictionary 性能可提升 5~10 倍。</p></blockquote>
<p class="maodian"></p><h2>🚫 五、常见误区</h2>
<p class="maodian"></p><h3>❌ 误区 1:认为dict = value是原子的</h3>
<div class="jb51code"><pre class="brush:csharp;">// 错误!这实际上是:
// if (exists) update; else add;
// 但中间可能被其他线程干扰
cache["key"] = newValue; // 不是原子操作!
</pre></div>
<p>✅ 正确做法:</p>
<div class="jb51code"><pre class="brush:csharp;">cache.AddOrUpdate("key", newValue, (k, old) => newValue);
</pre></div>
<p class="maodian"></p><h3>❌ 误区 2:在GetOrAdd中做非幂等操作</h3>
<div class="jb51code"><pre class="brush:csharp;">// 危险!工厂方法可能被多次调用(虽然最终只存一个结果)
var obj = cache.GetOrAdd("key", k => new ExpensiveObject()); // OK
// 更危险:有副作用的操作
cache.GetOrAdd("key", k =>
{
Log("Creating instance"); // 可能被记录多次!
return new MyService();
});
</pre></div>
<blockquote><p>💡 虽然最终值是唯一的,但<strong>工厂方法可能被多个线程同时调用</strong>(.NET 6+ 已优化为单次调用,但旧版本不一定)。建议工厂方法<strong>无副作用、幂等</strong>。</p></blockquote>
<p class="maodian"></p><h2>🛠 六、典型应用场景</h2>
<p class="maodian"></p><h3>1.内存缓存(Cache)</h3>
<div class="jb51code"><pre class="brush:csharp;">public class InMemoryCache
{
private readonly ConcurrentDictionary<string, object> _cache = new();
public T GetOrCreate<T>(string key, Func<T> factory)
{
return (T)_cache.GetOrAdd(key, k => factory());
}
}
</pre></div>
<p class="maodian"></p><h3>2.计数器 / 统计</h3>
<div class="jb51code"><pre class="brush:csharp;">private readonly ConcurrentDictionary<string, int> _hitCounts = new();
public void RecordHit(string page)
{
_hitCounts.AddOrUpdate(page, 1, (k, v) => v + 1);
}
</pre></div>
<p class="maodian"></p><h3>3.对象池(Object Pool)</h3>
<div class="jb51code"><pre class="brush:csharp;">private readonly ConcurrentDictionary<Type, Stack<object>> _pools = new();
public object Rent(Type type)
{
var pool = _pools.GetOrAdd(type, t => new Stack<object>());
return pool.TryPop(out var obj) ? obj : Activator.CreateInstance(type);
}
</pre></div>
<p class="maodian"></p><h2>📏 七、性能调优建议</h2>
<table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td>concurrencyLevel</td><td>预期并发更新线程数(默认为 CPU 核心数)</td></tr><tr><td>capacity</td><td>初始容量(避免频繁扩容)</td></tr></tbody></table>
<div class="jb51code"><pre class="brush:csharp;">// 预期 8 个线程并发写,初始存 1000 项
var dict = new ConcurrentDictionary<string, Data>(
concurrencyLevel: 8,
capacity: 1000
);
</pre></div>
<blockquote><p>💡 大多数场景用默认构造函数即可,除非你有明确的性能测试数据。</p></blockquote>
<p class="maodian"></p><h2>✅ 总结:何时使用ConcurrentDictionary?</h2>
<table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>多线程读写共享字典</td><td>✅ 强烈推荐</td></tr><tr><td>高频读 + 低频写(如缓存)</td><td>✅ 最佳选择</td></tr><tr><td>需要原子“获取或创建”语义</td><td>✅ 必选</td></tr><tr><td>单线程或只读场景</td><td>❌ 用普通 Dictionary 更轻量</td></tr><tr><td>需要保持插入顺序</td><td>❌ 考虑 ImmutableDictionary 或加锁的 SortedDictionary</td></tr></tbody></table>
<blockquote><p>🔑 记住:ConcurrentDictionary 不是万能的,但它是在并发字典场景下最高效、最安全的选择。</p></blockquote>
<p>到此这篇关于C# ConcurrentDictionary的使用小结的文章就介绍到这了,更多相关C# ConcurrentDictionary内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>C#并发容器之ConcurrentDictionary与普通Dictionary带锁性能详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]