volatile保证可见性:非volatile变量因线程本地缓存导致修改不可见,加volatile后强制主内存读写,使修改对所有线程立即可见;但不保证原子性。

直接验证 volatile 的可见性,不能靠“看代码觉得应该生效”,而要通过可复现的并发行为差异来证明——核心是构造一个非 volatile 变量会出问题、加上 volatile 就修复的场景,并用多线程反复运行暴露问题。
构造典型的可见性失效场景
让一个线程修改标志位(如 running = false),另一个线程循环检查该标志位是否为 false 来决定是否退出。若标志位未用 volatile 修饰,JVM 可能将该读操作优化为从线程本地工作内存(如寄存器或 CPU 缓存)中读取旧值,导致循环永不退出。
示例关键代码:
// 非 volatile 版本(可能无限循环)
private boolean running = true;
public void stop() {
running = false; // 线程 A 调用
}
public void run() {
while (running) { // 线程 B 死循环读取,可能永远看不到 false
// do something...
}
}
用多线程 + 循环执行放大问题概率
单次运行可能碰巧“看起来正常”,需反复执行多次并引入一定延迟或计算负载,增加主内存与工作内存同步被跳过的几率:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
立即学习“Java免费学习笔记(深入)”;
- 启动两个线程:A 执行
stop(),B 执行run() - 给 B 线程的循环体加入轻量计算(如
Math.sqrt(i++)),避免 JIT 过度优化掉整个循环 - 设置超时(如 5 秒),若 B 线程未在超时内退出,就判定为可见性失效
- 重复执行 100 次,统计“卡死”次数 —— 非 volatile 版本通常会出现 5%~30% 的失败率(取决于 CPU 和 JVM 版本)
对比 volatile 版本的行为差异
仅把字段改为 private volatile boolean running = true;,其余代码完全不变,再跑同样测试:
- 所有 100 次都会在毫秒级内正常退出
- 因为每次读
running都强制从主内存读取,每次写都立即刷回主内存 - 注意:volatile 不保证原子性(如
count++),这里只验证“读-改-写”中的“读可见”环节
辅助验证手段(非必需但更直观)
如果想进一步确认底层行为,可在 JDK 8+ 中配合 JVM 参数观察:
- 加
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(需 hsdis),查看编译后汇编是否有lock addl $0x0,(%rsp)类似内存屏障指令(volatile 写会插入 StoreStore + StoreLoad 屏障) - 用 JOL(Java Object Layout)工具查看字段在对象内存布局中是否因 volatile 出现 padding(防止伪共享,间接佐证 JVM 对 volatile 字段的特殊处理)

















