volatile通过内存屏障限制特定重排序:写后不许后续操作提前,读后不许前面操作延后;但不禁止非volatile变量间重排序,也不保证复合操作原子性。

volatile 本身不“禁止”所有指令重排序,而是通过建立内存屏障(Memory Barrier)来限制特定类型的重排序,并保证可见性与有序性。它的作用范围是编译器和处理器两个层面,但仅针对被修饰的变量本身及其相关读写操作。
volatile 如何影响重排序
Java 内存模型(JMM)规定:对 volatile 变量的写操作之后,不能将后续的普通读写操作重排序到该写之前;对 volatile 变量的读操作之前,不能将前面的普通读写操作重排序到该读之后。 这是通过在字节码或机器码生成时插入内存屏障实现的:
- volatile 写操作后插入StoreStore 屏障和StoreLoad 屏障,防止写后重排,也阻止写与后续读/写乱序
- volatile 读操作前插入LoadLoad 屏障和LoadStore 屏障,防止读后重排,也阻止读与后续读/写乱序
- 注意:volatile 读与 volatile 写之间不构成 happens-before 关系,除非是同一个变量的读写链(如写后读)
它不能禁止哪些重排序
volatile 不提供原子性,也不约束非 volatile 变量之间的重排序:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 两个普通变量之间的读写仍可能被重排,即使它们夹在 volatile 读写中间
- volatile 修饰的 long/double 的读写是原子的,但不意味着多线程下对该变量的复合操作(如 i++)是线程安全的
- 方法内联、循环无关代码移动等编译器优化,只要不破坏 volatile 的语义,仍可能发生
典型应用场景:双重检查锁中的单例
这是 volatile 防止重排序最经典的例子。若 instance 未用 volatile 修饰,可能导致线程看到一个已分配内存但尚未完成构造的对象引用(即指令重排导致 new 操作的三步:分配内存 → 调用构造器 → 赋值给引用,被重排为 分配内存 → 赋值 → 构造器):
- 使用 volatile 后,JVM 会在赋值语句后插入 StoreStore + StoreLoad 屏障,确保构造器执行完毕才让其他线程看到该引用
- 这不依赖 synchronized,但必须配合正确的初始化逻辑才能生效
和 synchronized 的区别
两者都提供有序性保障,但机制不同:
- synchronized 块的进入和退出分别对应 lock 和 unlock 操作,天然具备完整的 happens-before 语义,能禁止块内任意读写重排
- volatile 是轻量级的有序性+可见性保障,适用于“一个写、多个读”且无需互斥的场景(如状态标志位、停止信号)
- volatile 不能替代 synchronized 实现复合操作的原子性,比如 count++ 或 lazy-init 的完整流程
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










