volatile的读写屏障成对协作:写屏障强制刷新并失效其他缓存,读屏障强制重载最新值,二者共同建立happens-before关系,确保“写后读”的可见性与有序性。

Java 中的读屏障和写屏障不是孤立存在的,它们通过配合形成“写后读”的可见性链条,共同阻止多线程下的指令乱序与数据陈旧问题。关键不在于单个屏障有多强,而在于二者在特定语义(如 volatile 写 + 读)中成对出现、前后呼应。
写屏障确保“改得出去”
当一个线程对 volatile 变量执行写操作时,JVM 会在该写指令之后插入写屏障(Store Barrier)。它的实际作用是:
- 强制把当前 CPU 核心写缓冲区(store buffer)中所有待刷新的写操作(包括该
volatile写本身及它之前所有未刷出的普通写)立即同步到主内存; - 向其他 CPU 核心发送缓存失效通知(基于 MESI 等缓存一致性协议),让它们缓存中对应地址的数据标记为无效;
- 禁止编译器/JIT 将该写操作之后的读/写指令重排到它前面(即禁止 Store-Load、Store-Store 重排)。
读屏障确保“看得及时”
当另一个线程对同一个 volatile 变量执行读操作时,JVM 会在该读指令之前插入读屏障(Load Barrier)。它的实际作用是:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 清空当前核心的无效队列(invalidation queue),等待所有缓存失效消息处理完毕;
- 强制后续读操作必须从主内存(或最新一致的缓存行)重新加载数据,不能使用旧缓存值;
- 禁止编译器/JIT 将该读操作之前的读/写指令重排到它后面(即禁止 Load-Load、Load-Store 重排)。
协同机制:以 volatile 写-读为例
假设线程 A 执行 flag = true(flag 是 volatile),线程 B 执行 if (flag) { ... }:
- A 的写操作触发写屏障 → 把
flag = true及其之前所有写(比如data = 42)刷入主内存,并广播失效; - B 的读操作触发读屏障 → 等待收到 A 发出的失效通知,再从主内存加载
flag最新值; - 由于写屏障禁止“写后读”被提前,读屏障禁止“读后写”被延后,A 的写与 B 的读之间就建立了 happens-before 关系;
- 结果是:B 一旦看到
flag == true,就一定能读到 A 在写flag之前完成的所有写操作结果(如data == 42)。
不依赖 volatile 时的屏障缺失风险
若变量未声明为 volatile,JVM 不会自动插入读/写屏障:
- A 修改普通变量后,可能长期滞留在自己核心的写缓冲区,B 永远读不到;
- B 可能反复从自己缓存中读取旧值,即使主内存已被更新;
- 编译器或 CPU 还可能把 B 的读操作重排到 A 的写之前,导致逻辑彻底错乱(如双重检查单例中构造未完成就返回引用)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










