VarHandle 是 Java 9 引入的 JVM 层变量访问抽象,旨在安全替代 Unsafe;它支持 volatile 读写、原子更新及 acquire/release 语义,但仅限静态定位变量(字段、静态变量、数组元素),不支持局部变量或任意内存地址。

VarHandle 是什么,它真能替代 Unsafe 吗
VarHandle 不是语法糖,也不是 AtomicInteger 的简化版;它是 Java 9 引入的、JVM 层面支持的变量访问抽象,目标就是安全地暴露 Unsafe 的核心能力。它能做 volatile 读写、有序写、原子更新、甚至带获取/释放语义的访问——但前提是变量必须是可静态定位的(比如对象字段、静态字段、数组元素),不能是局部变量或任意内存地址。
常见错误现象:UnsupportedOperationException 抛在 VarHandle.ofField() 调用时,往往因为字段是 private 且未设 setAccessible(true),或字段类型不被支持(如原始类型包装类字段本身不被直接支持)。
- 只支持
public、protected、包级字段,或通过反射显式开放的private字段 - 不支持对 final 字段做原子更新(但可以 volatile 读)
- 数组 VarHandle 必须用
VarHandle.ofArray()构造,不能用ofField()
怎么拿到一个可用的 VarHandle 实例
核心就两条路:字段访问用 MethodHandles.privateLookupIn().findVarHandle(),数组访问用 VarHandle.ofArray()。Java 9+ 推荐前者,因为它绕过了反射权限检查的 runtime 开销,且更安全。
使用场景:你想原子更新某个对象的 int state 字段,又不想引入 AtomicInteger 包装开销,或者需要混合使用 acquire/release 语义。
立即学习“Java免费学习笔记(深入)”;
- 对于非 public 字段,必须用
MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())获取 lookup,再调用findVarHandle() -
VarHandle实例应缓存复用,构造开销不小,别每次操作都重新获取 - 不要尝试用
MethodHandles.lookup()直接查 private 字段——会抛IllegalAccessException
示例:
class Counter {
int value;
}
// 正确方式
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Counter.class, MethodHandles.lookup());
VarHandle vh = lookup.findVarHandle(Counter.class, "value", int.class);
compareAndSet 和 weakCompareAndSetPlain 有什么区别
这是最容易踩坑的地方:名字带 weak 的方法不保证失败重试一定能成功,它可能因平台内存模型“假失败”(spurious failure),但性能更高;而 compareAndSet 提供强保证,对应 x86 的 cmpxchg 指令,ARM 上则需额外屏障。
参数差异:weakCompareAndSet 系列有多个变体:weakCompareAndSetPlain(无内存语义)、weakCompareAndSetAcquire(读端语义)、weakCompareAndSetRelease(写端语义)。选错会导致可见性 bug。
- 业务逻辑要求“必须成功一次”的 CAS 场景(如初始化单例),只能用
compareAndSet - 计数器、信号量等允许“多试几次”的场景,优先用
weakCompareAndSetAcquire或weakCompareAndSetRelease -
weakCompareAndSetPlain几乎只用于内部 JVM 优化,应用层极少需要
为什么用了 VarHandle 还出现数据竞争
根本原因:VarHandle 只提供原子操作原语,不自动管理内存可见性边界。你调了 vh.set(obj, 1, Release),不代表其他线程立刻能看到这个 1——除非对方用 Acquire 或更强语义读取。
性能影响:过度使用 Acquire/Release 会抑制 CPU 指令重排,降低吞吐;全用 Plain 则退化为普通赋值,失去同步意义。
- 读写配对必须语义对齐:Release 写 + Acquire 读,才能建立 happens-before
- 对同一个字段混用不同内存模式(比如一个线程用
setOpaque,另一个用get),行为未定义 - 数组元素访问必须用数组专用 VarHandle,用字段 VarHandle 去操作数组会抛
ClassCastException
事情说清了就结束










