Interlocked专治单个变量的原子读写,lock用于多步逻辑的排他执行;前者无锁高效,后者支持复杂临界区,误用会导致性能下降或并发错误。
Interlocked,而不是 lock
直接说结论:Interlocked 专治「单个变量的原子读写」,比如计数器增减、标志位设置、引用替换;lock 是为「一段逻辑的排他执行」准备的,比如更新多个字段、读-改-写复合操作、涉及 I/O 或复杂校验的临界区。
常见错误现象:有人把 Interlocked.Increment(ref _count) 换成 lock 去保护一个 _count++,结果性能掉一截还毫无必要;也有人试图用 Interlocked 去保证「先检查余额再扣款」这种两步操作的原子性,结果出现超卖——因为 Interlocked 不提供「条件+动作」的原子组合能力。
Interlocked 底层靠 CPU 原子指令(如 LOCK XADD),无锁、无上下文切换、无等待,是真正的「无锁编程」基础lock 底层调用 Monitor.Enter/Exit,会触发内核态同步原语,线程争抢失败时可能挂起、调度,开销明显更高Interlocked;一旦涉及多个变量、判断分支、非原子表达式(如 x = x * 2 + 1),lock 或 Monitor 就不可替代Interlocked.CompareExchange 是无锁编程的核心入口C# 的无锁编程不是靠“不用锁”来定义的,而是靠「CAS(Compare-And-Swap)循环重试」实现的乐观并发控制。而 Interlocked.CompareExchange 就是这个机制的唯一公开出口。
它签名是:Interlocked.CompareExchange(ref int location1, int value, int comparand) —— 只有当 location1 == comparand 时,才把 value 写入,并返回旧值;否则不写,只返回当前值。整个过程原子。
int、long、IntPtr、引用类型(object 或泛型 T where T : class)CompareExchange 默认带 full memory barrier,但若需更细粒度(如仅 acquire/release),得用 Interlocked.CompareExchange(ref T, T, T, MemoryOrder) (.NET 8+)private int _state = 0; // 0=ready, 1=busy
public bool TryEnter()
{
return Interlocked.CompareExchange(ref _state, 1, 0) == 0;
}
// 成功返回 true,且 _state 已设为 1;失败说明已被别人抢先设为 1lock 和 Interlocked 性能差距有多大?在高竞争、高频更新场景下,差距非常真实:10 个线程对同一计数器做 100 万次递增,Interlocked.Increment 通常比 lock 快 3–5 倍,且 CPU 时间更稳,不会因线程挂起/唤醒抖动。
但这个优势只在「简单操作」上成立。一旦你把 Interlocked 套进复杂逻辑里强行“无锁”,比如在 CAS 循环里调用数据库、做字符串拼接、访问非原子字段,性能反而更差——因为自旋浪费 CPU,且逻辑本身已失去原子性保障。
Interlocked 操作本身不会阻塞,但你的业务逻辑如果包含阻塞点(如 await、File.Read、Thread.Sleep),那整个方案就不再是无锁了SpinLock(短临界区)、ReaderWriterLockSlim(读多写少)、或 Channels(生产者-消费者解耦)Interlocked 不保顺序,也不保可见性之外的语义很多人以为用了 Interlocked 就万事大吉

Interlocked.Exchange(ref obj, newObj) 确保引用替换原子,但不保证 newObj 的构造已完成(即可能看到部分初始化的对象),除非你在构造后才交换Interlocked 调用之间**没有顺序保证**:A 线程执行 Interlocked.Increment(ref x) 和 Interlocked.Increment(ref y),B 线程可能看到 y 先变、x 后变Thread.MemoryBarrier() 或用 volatile 字段(但 volatile 和 Interlocked 混用需格外小心)真正难的从来不是“怎么写无锁”,而是“怎么证明它在所有路径下都正确”。哪怕只多一步判断,就很可能得退回 lock。