Unsafe的内存屏障方法有loadFence()、storeFence()和fullFence(),分别对应x86的lfence、sfence、mfence指令,ARM64则映射为dmb ishld、dmb ishst、dmb ish。

Unsafe 的内存屏障方法有哪些,对应什么 CPU 指令
Unsafe 提供了三个内存屏障方法:loadFence()、storeFence() 和 fullFence()。它们不直接执行读/写操作,只插入编译器和 CPU 层级的屏障指令。在 x86 上:loadFence() 编译为 lfence(实际常被优化掉,因 x86 本身有强序读),storeFence() 对应 sfence,fullFence() 对应 mfence。ARM64 则分别映射为 dmb ishld、dmb ishst、dmb ish。注意:这些方法不保证跨平台语义完全一致,尤其在弱序架构上行为更关键。
什么时候必须用 Unsafe 内存屏障,而不是 volatile 或 synchronized
当你需要精细控制屏障位置,且无法接受 volatile 的“读-读/写-写”开销,或 synchronized 的锁开销时才考虑。典型场景是无锁数据结构(如自旋队列、MPSC 队列)中,仅需在指针更新后强制刷出写缓冲区,或在读取指针前确保后续字段读取不被重排序。例如:
node.next = newNode; // 普通写 UNSAFE.storeFence(); // 确保 node.next 对其他 CPU 可见,且不拖慢前面的计算 // 后续可安全发布 next 字段的引用
常见错误是误以为加了 storeFence() 就等价于 volatile 写——它不提供原子性,也不触发 JMM 的 happens-before 关系自动传播,必须成对设计(如写端 storeFence() + 读端 loadFence())。
如何安全获取 Unsafe 实例并规避 JDK 9+ 的模块限制
JDK 9 起 Unsafe 被移入 jdk.unsupported 模块,默认不可访问。启动 JVM 时需显式开放:
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
获取实例不能依赖 Unsafe.getUnsafe()(会抛 SecurityException),而要用反射绕过检查:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe UNSAFE = (Unsafe) f.get(null);
但要注意:JDK 17+ 进一步收紧反射权限,某些运行时(如 GraalVM Native Image)可能完全禁用该方式;生产环境应优先评估是否真需 Unsafe —— 多数场景用 VarHandle(JDK 9+)或 AtomicInteger.lazySet() 更稳妥。
与 VarHandle 内存顺序参数的对比和迁移建议
VarHandle 的 setRelease() / getAcquire() 语义上等价于 storeFence() + 普通写 / loadFence() + 普通读,且具备跨平台一致性保障。例如:
vh.setRelease(array, index, value); // ≈ store + storeFence int v = (int) vh.getAcquire(array, index); // ≈ loadFence + load
相比 Unsafe 手动插屏障,VarHandle 更安全、可读性更高,且被 JIT 更好优化。除非你在写底层并发原语库,否则应优先用 VarHandle 替代 Unsafe 的屏障调用。真正容易被忽略的是:即使用了 fullFence(),也不能替代对共享变量本身的原子操作——屏障只管顺序,不管竞态;读写仍需配合 CAS 或 volatile 字段才能保证正确性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










