从JDK 9起,Unsafe原子方法被废弃,JDK 17+默认禁止反射访问,运行时抛InaccessibleObjectException;VarHandle是其标准替代,通过MethodHandles.privateLookupIn构造,提供类型安全、内存语义明确的原子操作。

为什么不能直接用 Unsafe 的 compareAndSetObject 等方法了
从 JDK 9 开始,Unsafe 的原子字段操作(如 compareAndSetObject、getAndSetInt)被标记为废弃,JDK 17+ 默认禁止通过反射访问,运行时会抛出 InaccessibleObjectException。这不是“还能用但不推荐”,而是模块系统在启动时就拦截了对 Unsafe 非公开字段操作的反射调用。你写的 Unsafe.getUnsafe() 可能成功,但后续调用 unsafe.compareAndSetObject(...) 很可能直接失败——尤其在启用 --illegal-access=deny(JDK 16+ 默认)时。
VarHandle 是什么,它和 Unsafe 原子方法的映射关系
VarHandle 是 JDK 9 引入的标准替代机制,专为安全、跨平台、可优化的字段/数组/静态变量访问设计。它不暴露底层内存地址,而是通过类型化句柄 + 内存屏障语义来保证原子性。关键映射如下:
-
Unsafe.compareAndSetObject(obj, offset, expect, update)→varHandle.compareAndSet(obj, expect, update) -
Unsafe.getAndAddInt(obj, offset, delta)→varHandle.getAndAdd(obj, delta) -
Unsafe.getObjectVolatile(obj, offset)→varHandle.getVolatile(obj)
注意:VarHandle 不接受字节偏移量(offset),必须基于字段名或数组索引构建;所有操作都做类型检查,编译期就能捕获类型不匹配。
如何正确构造用于对象字段的 VarHandle
最常用的是通过 MethodHandles.privateLookupIn 获取私有字段访问权限,再用 findVarHandle 提取句柄。绕过 Lookup::in 权限限制是关键,否则会抛 IllegalAccessException。
- 必须用
MethodHandles.privateLookupIn(TargetClass.class, MethodHandles.lookup()),不能只用MethodHandles.lookup()直接查非本类字段 - 字段必须是
static或实例字段,且不能是final(除非是static final且已初始化,此时仅支持get,不支持原子更新) - 句柄类型由字段声明类型决定:比如
int field对应VarHandle的泛型签名是VarHandle(无泛型参数),实际调用时传int值即可
示例(获取 MyClass.flag 的原子更新句柄):
private static final VarHandle FLAG_HANDLE;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(MyClass.class, MethodHandles.lookup());
FLAG_HANDLE = lookup.findVarHandle(MyClass.class, "flag", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
使用 VarHandle 时容易忽略的内存模型细节
VarHandle 的每种操作隐含特定内存屏障,但不像 Unsafe 那样靠后缀(如 Volatile、Acquire)区分——它靠方法名本身定义语义:
-
get()/set():等价于普通读写(无屏障),仅保证可见性不强 -
getVolatile()/setVolatile():对应 full fence,等价于volatile字段访问 -
compareAndSet()/weakCompareAndSetPlain():前者带 acquire-release 语义;后者不保证 acquire,适合无依赖的计数场景,性能略高 - 没有直接对应
Unsafe.putOrdered*的方法,但setRelease()在多数场景下可替代(JDK 17+ 支持)
如果你之前习惯在无竞争路径用 Unsafe.putOrderedObject 减少屏障开销,现在得改用 setRelease() 并确认目标 JDK 版本 ≥ 17,否则该方法不存在。

















