什麼是 Interlocked.Exchange?
Interlocked.Exchange 是 System.Threading.Interlocked 類中的靜態方法,用於原子地替換(交換)一個變量的值,並返回該變量的舊值。整個讀-改-寫過程是不可分割的,在多線程環境中保證線程安全。
- 核心作用:無鎖地(
lock-free)將變量設置為新值,同時獲取舊值。 - 常見重載:
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?
普通的 “讀取值 + 賦值新值” 是兩步非原子操作,多線程下會因競態條件導致邏輯錯誤
- 典型問題:
// 非原子操作:讀取舊值和賦值新值之間可能被其他線程打斷
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緩存導致的 “髒讀”)。
基礎使用示例
最簡單的原子替換(返回原始值)
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
}
線程安全標誌位
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)
private IDisposable? _resource;
public void Dispose()
{
var res = Interlocked.Exchange(ref _resource, null);
res?.Dispose();
}
無鎖發佈對象(Safe Publication)
private object? _instance;
public object GetOrCreate()
{
if (_instance != null)
return _instance;
var newObj = new object();
Interlocked.Exchange(ref _instance, newObj);
return newObj;
}
多線程下的原子狀態切換
用 Exchange 實現線程安全的 “一次性初始化”(狀態從未初始化→已初始化,僅執行一次):
// 狀態枚舉: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 個線程執行初始化,其餘線程等待後直接返回對象:
線程3:執行初始化...
線程4:初始化已完成,直接返回對象
線程5:初始化已完成,直接返回對象
...(其餘線程均輸出此內容)
高級應用場景
單例模式(結合 Exchange 實現懶加載)
用 Exchange 替代 lock 實現高性能單例
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,實現 “取消後重置” 的線程安全邏輯:
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;
}
引用類型:原子替換對象引用(最常用)
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) |
對比示例
// Exchange:不管原來是什麼,都換
Interlocked.Exchange(ref state, 1);
// CompareExchange:只有 state == 0 才換
Interlocked.CompareExchange(ref state, 1, 0);
總結
Interlocked.Exchange 是 .NET 中最簡單、最快、最安全的“原子替換”操作,
它是實現一次性執行、無鎖狀態切換、安全對象發佈的基石。