Interlocked.Add是最直接的安全累加方案,因其将“读-改-写”封装为CPU级原子指令,无需锁且返回操作后新值,适用于计数器等简单整数累加场景。
为什么 Interlocked.Add 是最直接的安全累加方案
因为普通 ++ 或 += 在多线程下会丢失更新——两个线程同时读取同一变量值、各自加1、再写回,结果只加了1次而不是2次。interlocked.add 把“读-改-写”变成一条 cpu 级原子指令,不依赖锁,也不阻塞线程。
适用场景:计数器、统计总耗时、累计错误次数等无需复杂同步逻辑的整数累加。
-
Interlocked.Add只支持int、long、uint、ulong和指针类型;不能用于double或自定义类型 - 它返回的是**操作后的最新值**,不是旧值(这点和
Interlocked.Increment一致) - 在 .NET Core 3.0+ 和 .NET 5+ 中,对
long的操作在 x64 上是原生原子,在 x86 上由 CLR 保证原子性,不用额外处理
int counter = 0; // 多个线程并发调用 Interlocked.Add(ref counter, 1); // 安全 Interlocked.Add(ref counter, 5); // 同样安全
别用 lock 做简单累加,除非你真需要复合操作
用 lock 实现累加虽然能保证安全,但引入了线程等待、上下文切换和锁竞争开销。对单个整数累加这种极轻量操作,性能差距明显——实测在高并发下,Interlocked.Add 比 lock 快 3–5 倍以上。
常见误用:为“以后可能要扩展逻辑”提前上锁,结果把简单路径拖慢。
- 如果只是
counter += value,坚决用Interlocked.Add - 如果要同时更新多个变量(比如
sum和count),或者需要判断后再更新(如“仅当小于阈值才加”),才考虑lock或SpinLock -
lock对象别用this或字符串字面量,容易引发意外死锁或跨实例干扰
Interlocked.CompareExchange 能做更灵活的条件累加
当累加需要前置检查(比如“只在当前值小于 100 时才加 1”),Interlocked.Add 不够用,这时得靠 CompareExchange 手动实现 CAS 循环。
它本质是:比较当前值是否等于预期值,是则替换为新值并返回旧值;否则返回当前实际值,由你决定是否重试。
- 必须用循环包裹,因为可能被其他线程抢先修改导致失败
- 避免在循环里做耗时操作,否则浪费 CPU
- 注意不要无脑死循环,可加简单重试次数限制或
Thread.Yield()
int threshold = 100;
int current, newValue;
do {
current = counter;
if (current >= threshold) break;
newValue = current + 1;
} while (Interlocked.CompareExchange(ref counter, newValue, current) != current);
别忽略 volatile 和字段声明的影响
Interlocked 操作本身已包含内存屏障语义,所以累加目标字段**不需要**加 volatile 修饰符——加了反而误导,让人以为靠它就能保证线程安全。
真正要注意的是字段声明位置和生命周期:
- 静态字段累加需确认是否跨
AppDomain(.NET Framework)或AssemblyLoadContext(.NET Core+),否则可能不是同一个变量 - 实例字段累加时,确保所有线程操作的是同一个对象实例,而不是各自 new 出来的副本
- 局部变量不能用
ref传给Interlocked方法——编译器会报错,这是硬性限制
原子操作不是银弹。它快、轻量、适合整数,但也仅限于此。一旦涉及浮点运算、结构体更新或业务规则耦合,就得换思路——这时候想“怎么加得更安全”,不如先问“这个累加逻辑真的该放在这里吗”。










