什么是 Interlocked.Exchange?
Interlocked.Exchange 是 System.Threading.Interlocked 类中的静态方法,用于原子地替换(交换)一个变量的值,并返回该变量的旧值。整个读-改-写过程是不可分割的,在多线程环境中保证线程安全。
-
核心作用:无锁地(
lock-free)将变量设置为新值,同时获取旧值。 -
常见重载:
csharp
public static int Exchange(ref int location, int value);
public static long Exchange(ref long location, long value);
public static float Exchange(ref float location, float value);
public static double Exchange(ref double location, double value);
public static T Exchange<T>(ref T location, T value) where T : class; // 引用类型
public static object? Exchange(ref object? location, object? newValue);
-
参数:
-
location:要替换的共享变量(必须ref传递,直接操作内存地址)。 -
value / newValue:要写入的新值。
-
-
返回值:操作前的旧值。
底层基于 CPU 的原子交换指令(如 x86 的 xchg),性能极高。
为什么使用 Interlocked.Exchange?
普通的 "读取值 + 赋值新值" 是两步非原子操作,多线程下会因竞态条件导致逻辑错误
- 典型问题:
csharp
// 非原子操作:读取旧值和赋值新值之间可能被其他线程打断
private static int _flag = 0;
public static int UnsafeReplace(int newValue)
{
int oldValue = _flag; // 步骤1:读取旧值
_flag = newValue; // 步骤2:赋值新值
return oldValue; // 返回旧值
}
若两个线程同时执行上述代码,可能出现 "线程 A 读取旧值后,线程 B 已修改值,线程 A 最终返回的旧值与实际赋值前的旧值不一致" 的问题。 Interlocked.Exchange 将 "读取旧值 + 设置新值" 合并为不可中断的原子操作,从底层杜绝竞态条件,且无需阻塞线程(无锁)。
-
Exchange的优势:-
原子性:读取旧值和写入新值一步完成。
-
高性能:无锁,开销极低。
-
可见性:所有线程立即可见。
-
有序性:写入前的操作不会被重排到之后。
-
无锁:不阻塞线程。
-
适用场景:
-
线程安全的状态切换(如启用/禁用标志)。
-
实现懒加载单例(替换
null为实例)。 -
原子更新引用(如缓存对象)。
-
实现
SpinLock或自定义同步原语。
-
-
核心原理:CPU 级原子指令
Interlocked.Exchange 的原子性依赖 CPU 硬件指令:
-
x86架构:使用LOCK XCHG指令(LOCK前缀独占内存总线,阻止其他CPU核心修改该内存地址); -
ARM架构:使用SWP(交换指令)或LDXR/STXR(加载 - 存储独占)指令。
同时,该操作会触发全内存屏障(Full Memory fence):
-
保证操作前后的内存读写不会被
CPU重排序; -
确保所有线程能立即看到变量的最新值(避免
CPU缓存导致的 "脏读")。
基础使用示例
最简单的原子替换(返回原始值)
csharp
private static int _counter = 0;
public static void TestExchange()
{
// 目标:将_counter从0替换为100,获取原始值
int newValue = 100;
int originalValue = Interlocked.Exchange(ref _counter, newValue);
Console.WriteLine($"原始值:{originalValue},新值:{_counter}");
// 输出:原始值:0,新值:100
// 再次替换:将_counter从100替换为200
originalValue = Interlocked.Exchange(ref _counter, 200);
Console.WriteLine($"原始值:{originalValue},新值:{_counter}");
// 输出:原始值:100,新值:200
}
线程安全标志位
csharp
private int _isProcessing = 0; // 0=未处理, 1=处理中
public bool StartProcessing()
{
// 如果当前为0(未处理),则设为1(处理中)并返回true
return Interlocked.Exchange(ref _isProcessing, 1) == 0;
}
public void EndProcessing()
{
// 重置为未处理状态
Interlocked.Exchange(ref _isProcessing, 0);
}
一次性资源释放(防止重复 Dispose)
csharp
private IDisposable? _resource;
public void Dispose()
{
var res = Interlocked.Exchange(ref _resource, null);
res?.Dispose();
}
无锁发布对象(Safe Publication)
csharp
private object? _instance;
public object GetOrCreate()
{
if (_instance != null)
return _instance;
var newObj = new object();
Interlocked.Exchange(ref _instance, newObj);
return newObj;
}
多线程下的原子状态切换
用 Exchange 实现线程安全的 "一次性初始化"(状态从未初始化→已初始化,仅执行一次):
csharp
// 状态枚举:0=未初始化,1=初始化中,2=已初始化
private static int _initState = 0;
// 模拟初始化成本高的对象
private static ExpensiveObject? _expensiveObj;
/// <summary>
/// 线程安全的一次性初始化
/// </summary>
public static ExpensiveObject GetExpensiveObject()
{
// 1. 原子替换:将状态从0(未初始化)改为1(初始化中)
int originalState = Interlocked.Exchange(ref _initState, 1);
// 2. 只有原始状态是0的线程,执行初始化
if (originalState == 0)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}:执行初始化...");
_expensiveObj = new ExpensiveObject(); // 耗时初始化
// 3. 初始化完成,将状态改为2(已初始化)
Interlocked.Exchange(ref _initState, 2);
}
else
{
// 其他线程等待初始化完成
while (_initState != 2)
{
Thread.SpinWait(10); // 自旋等待(轻量级)
}
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}:初始化已完成,直接返回对象");
}
return _expensiveObj!;
}
// 测试:10个线程同时调用,仅1个线程执行初始化
var tasks = Enumerable.Range(0, 10)
.Select(_ => Task.Run(() => GetExpensiveObject()))
.ToList();
Task.WaitAll(tasks.ToArray());
// 模拟创建成本高的对象
public class ExpensiveObject
{
public ExpensiveObject()
{
Thread.Sleep(1000); // 模拟1秒耗时初始化
}
}
输出结果
仅 1 个线程执行初始化,其余线程等待后直接返回对象:
erlang
线程3:执行初始化...
线程4:初始化已完成,直接返回对象
线程5:初始化已完成,直接返回对象
...(其余线程均输出此内容)
高级应用场景
单例模式(结合 Exchange 实现懒加载)
用 Exchange 替代 lock 实现高性能单例
csharp
public sealed class LazySingleton
{
// 单例实例(引用类型)
private static LazySingleton? _instance;
// 初始化标记:0=未初始化,1=已初始化
private static int _initialized = 0;
private LazySingleton() { } // 私有构造
public static LazySingleton Instance
{
get
{
// 原子替换:将_initialized从0改为1,仅第一次调用返回0
if (Interlocked.Exchange(ref _initialized, 1) == 0)
{
_instance = new LazySingleton(); // 仅执行一次初始化
}
return _instance!;
}
}
}
取消令牌的原子重置
用 Exchange 原子替换 CancellationTokenSource,实现 "取消后重置" 的线程安全逻辑:
csharp
private static CancellationTokenSource? _cts = new CancellationTokenSource();
/// <summary>
/// 原子重置取消令牌(取消旧令牌,创建新令牌)
/// </summary>
public static CancellationToken ResetCts()
{
// 1. 原子替换旧的Cts为新实例,获取旧实例
CancellationTokenSource? oldCts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
// 2. 取消旧令牌(避免旧任务继续执行)
if (oldCts != null)
{
oldCts.Cancel();
oldCts.Dispose();
}
// 3. 返回新令牌
return _cts.Token;
}
引用类型:原子替换对象引用(最常用)
csharp
private ExpensiveObject? _cache = null;
public ExpensiveObject GetOrCreate()
{
// 如果为 null,原子替换为新实例
var newInstance = new ExpensiveObject();
var oldInstance = Interlocked.Exchange(ref _cache, newInstance);
return oldInstance ?? newInstance; // 如果旧值为 null,返回新实例
}
与 Interlocked.CompareExchange 的关系
| 方法 | 行为 |
|---|---|
| Exchange | 无条件替换 |
| CompareExchange | 条件替换(CAS) |
对比示例
csharp
// Exchange:不管原来是什么,都换
Interlocked.Exchange(ref state, 1);
// CompareExchange:只有 state == 0 才换
Interlocked.CompareExchange(ref state, 1, 0);
总结
Interlocked.Exchange 是 .NET 中最简单、最快、最安全的"原子替换"操作, 它是实现一次性执行、无锁状态切换、安全对象发布的基石。