Skip to content

volatile 这个关键字平时零零散散接触过——单例双重检查锁里见过、标志位也写过,但"它到底保证了什么、为什么不能保证原子性"一直没系统搞明白。这篇把 volatile 讲透:它的作用、背后的 Java 内存模型、以及和 synchronized 的配合。

一、volatile 的作用(核心)

volatile 就干两件事,还有一件干不了

① 保证可见性:一个线程改了 volatile 变量,其他线程立刻能看到最新值。

java
volatile boolean running = true;   // 加了 volatile

// 线程 A:running = false;  → 立刻刷回主内存
// 线程 B:while (running)   → 每次都从主内存读,能看到 false,退出循环

② 禁止指令重排(有序性):JVM 为了性能会重排指令,volatile 通过"内存屏障"禁止它周围的重排。

❌ 不保证原子性count++ 这种"读-改-写"三步操作,volatile 管不住中间被打断——两步之间别的线程也能进来。

java
volatile int count = 0;
count++;   // ❌ 三步操作,volatile 保证不了原子性,多线程下仍会丢数据

一句话记:volatile 管"看得见"(可见性)和"顺序对"(有序性),不管"不会被拆"(原子性)

二、为什么会有"看不见":Java 内存模型

可见性问题来自 Java 内存模型(JMM):每个线程有自己的工作内存(缓存),共享变量存在主内存里:

Java 内存模型:主内存 + 各线程的工作内存副本,volatile 强制读写经过主内存

线程读写变量,其实是先操作自己的工作内存副本,再同步到主内存。所以线程 A 改了 running,只是改了 A 的工作内存,可能还没刷回主内存;线程 B 读的还是自己工作内存里的旧值——两个线程各看各的副本,就"看不见"对方的修改volatile 强制读写都经过主内存,解决了这个问题。

三、volatile vs synchronized(一张表记住)

维度volatilesynchronized
保证可见性 + 有序性原子性 + 可见性 + 有序性
原子性❌ 不保证✅ 保证
是否阻塞否(无锁)是(加锁,可能阻塞)
性能较低
适用标志位、单次读写复合操作、临界区

选型:只是"一个线程写、其他线程读"的标志位,用 volatile(轻量、无锁);要保证"读-改-写"的原子性,用 synchronized 或原子类(AtomicInteger)。

四、典型应用场景

① 状态标志位(最常见):

java
volatile boolean running = true;
public void stop() { running = false; }   // 停止线程
public void run() { while (running) { ... } }

② 单例双重检查锁(DCL):靠 volatile 禁止"引用先指向、初始化延后"的重排,保证拿到的一定是完整对象(见《单例模式》的双重检查锁那节)。

小结

  • volatile 管两件事:可见性(读写经过主内存)+ 有序性(禁止指令重排)
  • 不保证原子性——count++ 这类复合操作管不住
  • 标志位用 volatile(轻量),复合操作用 synchronized/原子类

想了解 volatile 在并发里的完整定位(和 synchronized、CAS 一起怎么配合),看《Java 并发编程》;想了解可见性背后的 JMM 主内存/工作内存模型,看《JVM 内存模型与垃圾回收》。