VarHandle.compareAndExchange 本质是原子读-改-写并返回旧值,避免二次volatile读与逻辑错位,类型安全、无装箱、JVM直译最优指令,需正确初始化并缓存,内存语义需按场景选型。

VarHandle.compareAndExchange 本质是原子读-改-写 + 返回旧值
它和 AtomicInteger.compareAndSet 表面相似,但关键区别在于:后者只返回 true/false,而前者**一定返回被替换前的原始值**。这意味着你无需额外调用 get() 就能拿到“上一次的值”,避免了两次 volatile 读的开销,也消除了中间被其他线程修改导致的逻辑错位风险。
- 适用于需要基于旧值做分支判断再决定后续行为的场景(比如限流器中检查当前计数、状态机中校验前序状态)
- 返回值类型严格匹配变量声明类型,不会发生隐式装箱/拆箱(
AtomicInteger的compareAndSet接收int但内部涉及Integer对象比较,有陷阱) - 不依赖
Unsafe的具体实现路径,而是由 JVM 直接生成最优的 CPU 指令(如 x86 的cmpxchg),可移植性更强
必须用 MethodHandles.lookup() 正确获取 VarHandle 实例
直接 new 或静态缓存一个通用 handle 是行不通的。每个 VarHandle 绑定到特定类、字段名和类型三元组,且访问权限在构造时就已固化。
- 访问
public字段:用MethodHandles.lookup().in(cls).findVarHandle(cls, "field", type) - 访问
private字段:必须用MethodHandles.privateLookupIn(cls, MethodHandles.lookup()),否则抛IllegalAccessException - 数组元素访问:用
MethodHandles.arrayElementVarHandle(int[].class),不是靠字段反射 - 务必把
VarHandle实例缓存为static final字段——反复调用findVarHandle开销大,且可能触发类加载锁
compareAndExchangeAcquire / Release 版本要按内存语义选
普通 compareAndExchange 提供的是 volatile 语义(即 acquire + release),但如果你的场景有更精细的同步需求,就得换带后缀的版本:
-
compareAndExchangeAcquire:仅保证后续读操作不重排到该操作之前(适合“读取状态后进入临界区”) -
compareAndExchangeRelease:仅保证前面的写操作不重排到该操作之后(适合“退出临界区前更新状态”) - 误用会破坏正确性:比如在释放锁时用了
Acquire,可能导致临界区内的写对其他线程不可见 - Java 9+ 才支持这些方法;Android Runtime(ART)直到 API 33+ 才完整支持
VarHandle内存顺序变体
和 AtomicReference.compareAndExchange 对比时注意泛型擦除
AtomicReference<T>.compareAndExchange 看似功能一致,但它底层仍走对象引用比较,且泛型在运行时被擦除。而 VarHandle 可以直接操作原始类型数组元素、堆外内存(配合 MemorySegment)、甚至 Unsafe 替代路径下的字段——这才是“更细粒度”的真正来源。
- 例如想原子更新
int[]中第 5 个元素:用arrayHandle.compareAndExchange(array, 4, expect, update),没有对象包装、无 GC 压力 - 而
AtomicIntegerArray虽然也支持数组,但其compareAndSet不返回旧值,且内部仍维护一份int[]引用,无法绕过 JVM 堆管理 - 跨进程共享内存或 JNI 边界场景下,
VarHandle配合MemorySegment是目前唯一标准、安全的原子操作入口

















