volatile 关键字 📅 2026/8/9 5:16:33 volatile关键字的基本概念一、问题背景CPU 缓存带来的可见性问题现代 CPU 为了提升性能每个核心都有自己的高速缓存Cache。线程运行在不同核心上时读取变量可能读的是各自缓存里的副本而不是主内存中的最新值。1.1 没有 volatile 时的代码public class VisibilityProblem { // 普通变量无 volatile private boolean running true; public void stop() { running false; // 线程 A 修改 } public void doWork() { while (running) { // 线程 B 读取 // 执行任务... } System.out.println(已停止); } }问题场景线程 A 调用stop()把running改为false线程 B 之前读取的是 running ture, 导致while (running)循环可能永远停不下来1.2 为什么会这样┌─────────────┐ ┌─────────────┐ │ 线程 A │ │ 线程 B │ │ (核心 1) │ │ (核心 2) │ └──────┬──────┘ └──────┬──────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ 缓存副本 A │ │ 缓存副本 B │ │ runningfalse │ │ runningtrue │ ← 线程 B 还在读旧值 └──────┬───────┘ └──────┬───────┘ │ │ └───────────┬───────────┘ ▼ ┌──────────────┐ │ 主内存 │ │ runningfalse│ ← 线程 A 已写入但线程 B 的缓存未失效 └──────────────┘每个线程都有自己的工作内存主内存 flagfalse | ------------------- | | 线程A缓存 线程B缓存 flagfalse flagtrue线程 A 修改flagfalse可能只是修改了自己的缓存没有及时刷新到主内存。核心问题线程 A 修改了主内存的值但线程 B 的缓存副本不会自动失效导致线程 B看不到最新值。这就是可见性Visibility问题。二、volatile 的诞生解决可见性Java 设计者意识到程序员需要一个关键字告诉 JVM这个变量的每次读写都必须直接操作主内存不要依赖缓存。于是volatile被创建出来。2.1 加上 volatile 后public class VisibilityFixed { // 加上 volatile private volatile boolean running true; public void stop() { running false; // 线程 A 写入 → 直接刷回主内存 } public void doWork() { while (running) { // 线程 B 读取 → 直接从主内存重新加载 // 执行任务... } System.out.println(已停止); } }效果线程 A 写入running false时会立即刷新到主内存线程 B 读取running时会使本地缓存失效重新从主内存读取这样线程 B 就能立刻看到false循环正常退出。三、volatile 的第二个作用禁止指令重排序3.1 什么是指令重排序编译器和CPU为了提高执行效率可能会调整代码的执行顺序。在单线程下这没问题但在多线程下可能引发灾难。3.2 没有 volatile 时的重排序问题public class ReorderingProblem { private int value 0; private boolean ready false; // 线程 A 执行 public void writer() { value 42; // ① ready true; // ② } // 线程 B 执行 public void reader() { if (ready) { // ③ System.out.println(value); // ④ } } }没有 volatile 时编译器/CPU 可能把 ① 和 ② 重排序// 实际执行顺序可能变成 ready true; // ② 先执行 value 42; // ① 后执行后果线程 B 看到ready true但value还是0程序输出0而不是预期的423.3 volatile 如何禁止重排序volatile会在读写操作前后插入内存屏障Memory Barrier强制保证执行顺序线程 A 的 writer(): value 42; ──[StoreStore 屏障]── // 保证 value 的写入在 ready 之前 ready true; // volatile 写 ──[StoreLoad 屏障]── // 保证此操作之前的写入对其他线程可见 线程 B 的 reader(): if (ready) { // volatile 读 ──[LoadLoad 屏障]── // 保证 ready 的读取在 value 之前 System.out.println(value); }内存屏障的本质一道栅栏阻止指令越过它进行重排序。四、volatile 不能做什么重要4.1 volatile 不能保证原子性public class NotAtomic { private volatile int count 0; // 线程 A 和线程 B 同时执行 public void increment() { count; // 这不是原子操作 } }count实际上分三步读取count 的值加 1写回count即使加了volatile这三步之间仍可能被其他线程打断时间点 线程 A 线程 B t1 读取 count0 t2 读取 count0 t3 计算 011 t4 计算 011 t5 写入 count1 t6 写入 count1 最终结果count 1期望是 2结论volatile解决不了原子性问题需要用synchronized或AtomicInteger。五、总结volatile 到底做了什么特性是否保证原理可见性✅ 保证读写直接操作主内存缓存失效机制禁止重排序✅ 保证内存屏障阻止指令乱序原子性❌ 不保证i这类复合操作仍需同步5-1、什么时候用 volatilevolatile 通常用于一个变量被多个线程共享并且一个线程修改后其他线程需要立即感知的场景。它保证变量的可见性和有序性但不保证复合操作的原子性所以不能替代锁// ✅ 适合用 volatile 的场景 // 1. 状态标志位一个线程写其他线程读 private volatile boolean isRunning true; // 2. 双重检查锁DCL中的单例 private volatile static Singleton instance; // 3. 读多写少且写入不依赖当前值 private volatile long configVersion;1、场景一状态标志位最常见public class Worker { private volatile boolean running true; public void work(){ while(running){ // 执行业务 } System.out.println(线程结束); } public void stop(){ runningfalse; } }2、场景二单例模式双重检查锁public class Singleton { private volatile static Singleton instance; public static Singleton getInstance(){ // 第一重检查 if(instancenull){ synchronized(Singleton.class){ // 第二重检查 if(instancenull){ instancenew Singleton(); } } } return instance; } }为什么需要 volatile重点instancenew Singleton();不是一步完成。实际上第一步分配对象内存memory new Object()第二步初始化对象memory.namexxx第三步让 instance 指向对象instance memory但是 JVM 可能发生指令重排序变成1. 分配内存 3. instance 指向内存 2. 初始化对象于是线程 Ainstance ! null以为对象创建好了。但是对象还没有初始化完成。导致空指针。volatile 禁止这种重排序。3、场景三配置刷新public class Config { private volatile String address; public void update(String newAddress){ addressnewAddress; } public String getAddress(){ return address; } }多个线程读取配置线程1 修改地址 线程2、3、4立即看到新地址适合 volatile。5-2、什么时候不用 volatile// ❌ 不适合的场景 // 1. 需要原子性操作的计数器 private volatile int counter; // 错误count 非原子 // 2. 多个线程同时修改同一个变量 // 应该用 AtomicInteger 或 synchronized5-3、volatile 和 synchronized 区别volatilesynchronized可见性✅✅原子性❌✅有序性✅✅加锁❌✅性能高相对低volatile我只是告诉 JVM这个变量变化后大家马上看到。synchronized我不仅保证大家看到还保证同一时间只有一个线程修改。六、一句话记住 volatilevolatile告诉 JVM这个变量是共享的、易变的每次读取都要去主内存拿最新值每次写入都要立刻刷回主内存并且不要打乱它周围的指令顺序。但它不保证复合操作的原子性——那是synchronized和java.util.concurrent.atomic包该做的事。