volatile 是 java 中用于解决可见性和禁止部分重排序的语义工具,通过强制读写主内存及插入内存屏障实现,其核心依据是 jmm 中“volatile 写 happens-before 后续同变量读”的 happens-before 规则。

volatile 关键字在 Java 中不是“轻量级 synchronized”,而是专门解决可见性和禁止部分重排序的语义工具;它和 JMM 的 happens-before 原则是紧密绑定的——volatile 的写操作 happens-before 后续对同一变量的读操作,这是 JMM 明确规定的八大规则之一,也是 volatile 保证线程间通信可靠性的根本依据。
volatile 解决什么问题?
在没有同步手段的多线程场景下,一个线程修改了普通变量,另一个线程可能长期看不到新值。原因有二:
- 变量副本存在于各线程的工作内存中,修改后未必立即刷回主内存
- 编译器或 CPU 可能对无依赖的操作重排序,导致“写变量 A”被排到“写标志位 flag”之后,而其他线程却先看到 flag = true,再读到旧的 A 值
volatile 正是为这两个问题提供最小但精准的约束:它不保证原子性(如 i++ 仍需 synchronized 或 AtomicInteger),但强制每次读都从主内存取最新值,每次写都立即刷新到主内存,并插入相应的内存屏障防止特定方向的重排序。
happens-before 是怎么靠 volatile 起作用的?
happens-before 不是时间先后,而是一种逻辑先行关系:如果 A happens-before B,那么 A 的结果对 B 可见,且 JVM 不能把 B 重排到 A 之前执行。
volatile 变量规则直接建立这种关系:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 线程1 执行
flag = true;(volatile 写)→ 这个操作 happens-before - 线程2 执行
if (flag) { ... }(volatile 读)
于是线程2 在读到 true 时,一定能“看到”线程1 在此之前对所有变量(包括非 volatile 的)所做的修改——前提是这些修改发生在 volatile 写之前,且没有被重排序破坏顺序。这就是 volatile 的“传递可见性”能力,本质来自其背后的内存屏障(StoreLoad 屏障)。
典型误用:volatile 不能替代锁的场景
虽然 volatile 提供 happens-before 保障,但它只作用于单个变量的读写,不构成临界区保护。以下情况它无效:
-
counter++:包含“读-改-写”三步,volatile 无法保证这三步整体原子 - 多个 volatile 变量之间的协作:比如先写
data = xxx,再写ready = true,即使都加 volatile,也不能保证其他线程看到ready == true时data一定就绪——除非用同一个 volatile 变量串起逻辑,或配合锁 - 复合判断:
if (flag && value > 0)中,flag是 volatile,value不是,那value > 0的读取仍可能读到过期值
和 synchronized 的 happens-before 对比
两者都建立 happens-before 关系,但机制和粒度不同:
- synchronized:解锁(unlock)happens-before 后续同锁的加锁(lock)。它以锁为边界,包裹整个临界区,天然保证块内所有操作的原子性、可见性、有序性
- volatile:仅针对单个变量,写 → 读之间建立 happens-before,开销更小,适合状态标志、一次性发布等简单通信场景
它们可以组合使用:例如 DCL(双重检查锁定)中,instance 字段声明为 volatile,确保 new Singleton() 的初始化完成(包括构造函数内所有赋值)对后续读操作可见,避免返回半初始化对象。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










