c# C#中的强引用、弱引用、软引用和线程安全

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

C# 中没有“软引用”(SoftReference)这个概念,这是 Java 的术语;C# 只有强引用、弱引用(

WeakReference
WeakReference<t></t>
),以及通过
ConditionalWeakTable
实现的类似“附带生命周期”的弱绑定——它不叫软引用,也不提供内存压力敏感的自动回收机制。

强引用 vs 弱引用:对象存活判定的根本区别

强引用是默认行为:

var obj = new object();
—— 只要该引用在作用域内且未被置为
null
,GC 就绝不会回收它,哪怕内存紧张。

弱引用则不同:

WeakReference wr = new WeakReference(new object());
—— 它不阻止 GC 回收目标对象。下次 GC 后调用
wr.Target
可能返回
null

实际使用中要注意:

WeakReference<t></t>
是泛型版本,类型安全,避免装箱,推荐替代非泛型
WeakReference
获取值前必须检查是否还存活:
if (wr.TryGetTarget(out MyType obj)) { /* 使用 obj */ }
,不能直接访问
Target
属性后判空,因为中间可能已被回收
弱引用本身(
WeakReference
实例)是强引用的,它自己不会被 GC 掉,但它的
Target

为什么 C# 没有 SoftReference?替代方案是什么

C# 运行时(CLR)的 GC 不暴露“内存压力阈值”或“最近最少使用(LRU)淘汰策略”给用户代码,因此无法实现 Java 那种“内存不足时才回收”的软引用语义。

如果你需要缓存且希望自动释放,可用以下方式逼近类似效果:

MemoryCache
Microsoft.Extensions.Caching.Memory
):支持绝对过期、滑动过期、大小限制和回调(
PostEvictionCallbacks
),是生产环境首选
手动结合
GC.GetTotalMemory(false)
+
GC.CollectionCount(2)
做粗略监控(不推荐用于决策,仅作参考)
避免自行实现“软引用容器”:容易因判断不准导致 OOM 或频繁重建开销

注意:

ConditionalWeakTable<tkey tvalue></tkey>
常被误认为“软引用”,其实它是线程安全的弱键映射表,
TKey
被回收时对应条目自动移除,但它不控制
TValue
的生命周期,也不是缓存机制。

弱引用与线程安全:常见误区和正确姿势

WeakReference
WeakReference<t></t>
本身是线程安全的——
TryGetTarget
可并发调用,不会崩溃或返回损坏对象。

但“线程安全”不等于“业务安全”:

即使
TryGetTarget
成功返回了对象,该对象仍可能在你开始使用前被其他线程触发的 GC 回收(虽然概率低,但在高并发+高频 GC 场景下不可忽视)
若对象本身不是线程安全的(如
List<t></t>
),多个线程同时操作它仍需加锁或改用
ConcurrentBag<t></t>
等并发集合
ConditionalWeakTable
是线程安全的,可用于存储元数据(如 AOP 的拦截器上下文),但它的键必须是引用类型,且键被回收后条目异步清理(不保证立即)

典型错误写法:

var obj = wr.Target as MyType; // ❌ 非原子操作,Target 可能在 as 前被回收
正确写法始终用
TryGetTarget

弱引用不是银弹。它解决的是“持有引用但不想阻碍回收”的特定场景(如事件监听器解耦、大对象缓存、图形资源管理),滥用会导致难以调试的空引用异常。真正需要缓存语义时,优先选

MemoryCache
;需要绑定生命周期时,先确认
ConditionalWeakTable
是否满足需求,而不是硬套弱引用。

相关推荐