Interlocked不能直接操作普通变量,因其所有方法均要求ref参数以获取内存地址来执行CPU原子指令;Increment/Decrement仅支持±1整数增减,Add支持任意增量且返回新值;CompareExchange是CAS基础,用于实现无锁编程;Read用于long原子读,Or/Xor用于标志位操作。

Interlocked 为什么不能直接操作普通变量
因为 Interlocked 的所有方法都要求传入 ref int、ref long 等可寻址的变量,它底层依赖 CPU 的原子指令(如 XCHG、LOCK XADD),必须能拿到变量的内存地址。如果你传一个属性、临时计算值或只读字段,编译器会报错:Cannot pass 'xxx' as a ref or out argument because it is not a variable。
实操建议:
- 确保操作的是类字段或局部变量,且类型为
int、long、IntPtr、object或泛型T(.NET 6+ 的Interlocked.CompareExchange<t></t>) - 不要试图对
struct字段直接调用,比如Interlocked.Increment(ref myObj.Counter)是合法的,但Interlocked.Increment(ref myObj.SomeStruct.Value)可能因不可寻址而失败 - 避免在属性 getter 中包装
Interlocked调用——这掩盖了线程安全边界,也容易误以为“属性本身是线程安全的”
Increment/Decrement 和 Add 的区别在哪
Interlocked.Increment 和 Interlocked.Decrement 是特化版本,语义清晰、性能略优,仅支持 ±1;而 Interlocked.Add 更通用,支持任意整数增量,且在 .NET Core 2.0+ 后已支持 long 和 int,还新增了 Interlocked.Add(ref long, long) 的重载。
常见错误现象:用 Increment 实现计费扣减 0.5 元?不行,它只接受整数。此时必须用 Interlocked.Add(ref balance, -50)(单位为分)或改用 double 配合 CompareExchange 手动实现。
实操建议:
- 计数器类场景(如请求量统计)优先用
Increment/Decrement,语义直白,JIT 也可能做额外优化 - 金额、偏移量等需非 ±1 修改时,统一用
Interlocked.Add,别自己写循环 +CompareExchange - 注意
Add返回的是**变更后**的值,不是旧值(这点和Increment一致)
CompareExchange 是万能原子操作基座
几乎所有高级原子逻辑(如无锁栈、CAS 循环、延迟初始化)都基于 Interlocked.CompareExchange。它本质是:“如果当前值等于预期值,就设成新值,并返回原值;否则只返回当前值,不修改”。这是唯一能实现“读-改-写”原子性的入口。
使用场景举例:实现线程安全的懒加载单例
private static MyService _instance;
private static readonly object _lock = new();
public static MyService Instance
{
get
{
if (_instance == null)
{
var instance = new MyService();
Interlocked.CompareExchange(ref _instance, instance, null);
}
return _instance;
}
}
这个写法比 lock 轻量,但要注意:多个线程可能同时创建 MyService 实例,只有第一个成功写入的被保留,其余被丢弃(所以构造函数不能有副作用)。
实操建议:
- 写 CAS 循环时,务必把
CompareExchange放在 do-while 内部,反复读取当前值并尝试更新 - .NET 6+ 提供了
Interlocked.CompareExchange<t>(ref T, T, T)</t>,可用于引用类型或不可变结构体,但要求T是无指针类型(no unsafe pointers) - 不要用
CompareExchange操作浮点数(float/double)做精确比较——NaN、-0.0 等会导致意外交替失败
Read、Or、Xor 这些冷门方法什么时候用
Interlocked.Read 专用于 long 在 32 位环境下的原子读取(x86 下 long 读写非原子,可能读到撕裂值),.NET Core / .NET 5+ 在 x64 上已默认原子,但仍建议保留以保兼容;Or 和 Xor 常用于标志位管理,比如多线程协同设置状态掩码。
性能影响:这些操作都是单条 CPU 指令,开销远低于锁,但频繁调用仍会引发缓存行争用(false sharing)。例如把几个高频更新的 int 字段放在同一个类里,它们可能共享 L1 缓存行,导致性能反降。
实操建议:
- 跨平台项目中读
long字段,坚持用Interlocked.Read(ref field),别依赖运行时架构判断 - 用
Or设置标志位(如Interlocked.Or(ref flags, (int)Flags.Ready)),用Xor切换开关(如翻转调试模式) - 若字段间无逻辑关联,给它们加
[StructLayout(LayoutKind.Sequential)]和[FieldOffset]手动隔离,或插入long填充字段防 false sharing
ConcurrentQueue 这类高层抽象。过早手写 Interlocked 循环,反而容易漏掉内存屏障语义或引入 ABA 问题。










