volatile通过强制主内存读写和内存屏障保证可见性,但不保证原子性;它建立happens-before关系,使写前操作对后续读线程可见,依赖实际读写动作而非自动同步。

volatile 为什么能保证内存可见性
因为 volatile 强制线程每次读取变量都从主内存重新加载,每次写入都立即刷新回主内存,绕过了 CPU 缓存的本地副本。JVM 在生成字节码时,会对 volatile 读写插入内存屏障(Memory Barrier):读操作前加 LoadLoad 和 LoadStore,写操作后加 StoreStore 和 StoreLoad,阻止指令重排序并确保可见性传播。
注意:它不提供原子性——i++ 这种复合操作即使作用于 volatile int i,依然可能丢失更新。
volatile 不能替代 synchronized 的典型场景
当需要「读-改-写」原子性时,volatile 失效。比如计数器自增、状态标志配合业务逻辑判断后再操作等。
-
volatile boolean flag = false;可用于通知线程退出,但若写成if (flag) doSomething(); flag = true;,就存在竞态——两线程同时通过 if 判断后都执行doSomething() -
volatile int count;无法安全执行count++,因为底层是getfield → iconst_1 → iadd → putfield三步,中间可能被其他线程打断 - 对象引用虽用
volatile修饰,但其内部字段修改仍不具可见性,例如volatile List,后续list = new ArrayList(); list.add("a")不会触发对其他线程的可见保障
volatile 与 happens-before 规则的直接关联
JMM 中,对一个 volatile 变量的写操作,happens-before 于任意后续对该变量的读操作。这是唯一一条由 Java 语言规范明确定义的、不依赖锁的 happens-before 关系。
立即学习“Java免费学习笔记(深入)”;
本文档主要讲述的是Android 本地数据存储;对于需要跨应用程序执行期间或生命期而维护重要信息的应用程序来说,能够在移动设备上本地存储数据是一种非常关键的功能。作为一名开发人员,您经常需要存储诸如用户首选项或应用程序配置之类的信息。您还必须根据一些特征(比如访问可见性)决定是否需要涉及内部或外部存储器,或者是否需要处理更复杂的、结构化的数据类型。跟随本文学习 Android 数据存储 API,具体来讲就是首选项、SQLite 和内部及外部内存 API。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以
这意味着:如果线程 A 写了 volatile boolean ready = true;,线程 B 后续读到 ready == true,那么线程 A 在写 ready 之前的所有内存操作(包括非 volatile 字段赋值),对线程 B 也一定可见。
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 非 volatile 写
flag = true; // volatile 写 → 建立 happens-before 边界
}
public void reader() {
if (flag) { // volatile 读
System.out.println(a); // 此处一定能打印 1
}
}
}
常见误判:volatile 能防止指令重排序,但不等于“顺序执行”
它只禁止特定类型的重排序(编译器和处理器不会把 volatile 读/写与前后某些操作乱序),但不保证多线程下所有语句的全局执行顺序一致。比如两个线程分别写不同的 volatile 变量,它们之间的相对顺序对第三方线程而言仍是不确定的。
真正容易被忽略的是:volatile 的可见性保障,依赖于「至少有一个线程执行了 volatile 写,另一个线程执行了 volatile 读」。如果读线程从未读取该变量,或者写线程写完后读线程才启动且未触发 volatile 读,那之前的写操作对其不可见——不是“一写全知”,而是“读到才同步”。









