bigywater.tech
Java · 177

VarHandle 内存序:opaque、acquire、release、volatile

VarHandle 提供了 plain、opaque、acquire/release、volatile 四档内存序。它们的差别能直接测出来吗?这篇用 jcstress 做消息传递测试,再用一个会「永远看不到写入」的热循环验证可见性的差别,并且如实说明:顺序重排这一项,这台机器上没有测出来(下一篇 final 字段与安全发布也会用到这些概念)。

0
四种写法里 (y=1, x=0) 出现的次数(Release/Acquire 与 volatile 有保证)
3 秒仍未结束
热循环里的普通读看不到另一个线程的写入
约 305 ms
opaque / acquire / volatile 读看到写入

消息传递测试

实测 · 本机执行输出
  JDK 21,jcstress 0.16,default 模式(整个运行 154 秒),Apple Silicon(arm64)
  各写法下四种结果(y, x)出现的次数:
    普通读写(x = 1; y = 1 / r1 = y; r2 = x)             状态 OK      (0, 0) 1,929,905  (0, 1) 19,856  (1, 1) 72,421,329
    Opaque(setOpaque / getOpaque)                   状态 OK      (0, 0) 106,190,005  (0, 1) 28,705  (1, 1) 22,362,416
    Release / Acquire(y 用 setRelease / getAcquire)  状态 OK      (0, 0) 33,343,067  (0, 1) 10,839  (1, 0) 0  (1, 1) 33,511,265
    volatile(x、y 都是 volatile)                       状态 OK      (0, 0) 93,859,864  (0, 1) 39,958  (1, 0) 0  (1, 1) 71,658,469

可见性

实测 · 本机执行输出
plain:3 秒后读线程仍然没有看到写线程在 300 ms 时写入的值(循环没有结束)
opaque:循环在 304 ms 后结束,看到了写入(空转 多次)
acquire:循环在 308 ms 后结束,看到了写入(空转 多次)
volatile:循环在 304 ms 后结束,看到了写入(空转 多次)

分步演示

VarHandle 内存序
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,jcstress 0.16(Maven 解析),Apple Silicon(arm64)
    • 没有验证:x86 与其它架构上普通读写是否会出现 (1, 0)、jcstress 的更长时间模式(tough)、acquire/release 在更复杂的多变量模式(如 IRIW、store buffering)下的行为、setOpaque 与 setRelease 的写写顺序区别、普通读的循环在不同 JIT 阶段(C1/解释器)下的行为;可见性实验只验证了热循环这一种场景
    京ICP备2026067502号-1京公网安备11010502063487号