volatile不能保证线程安全,仅保证可见性(通过MESI协议强制主存同步)和有序性(通过内存屏障禁止指令重排),但不保证原子性;i++等复合操作因含读-改-写三步仍存在竞态条件。

volatile 关键字不能保证线程安全,它只解决部分问题——主要是可见性和有序性,但不保证原子性。
volatile 保证可见性:让修改“被看到”
Java 线程默认把变量缓存在自己的工作内存(如 CPU 缓存或寄存器)中,不一定实时同步主内存。这会导致一个线程改了值,另一个线程还在用旧缓存,永远看不到更新。
volatile 强制:
- 每次读 volatile 变量,都直接从主内存加载最新值;
- 每次写 volatile 变量,都立即刷回主内存,并通知其他 CPU 失效对应缓存行(通过 MESI 协议)。
典型例子:状态标志 private volatile boolean running = true;。子线程 while(running) 循环能及时感知主线程 shutdown() 中的 running = false。
volatile 禁止指令重排序:保障执行顺序
编译器和 CPU 为优化性能可能重排代码,比如单例双重检查中:
① 分配内存 → ② 初始化对象 → ③ 赋值引用。
若重排为 ①→③→②,其他线程可能拿到未初始化完成的对象。
volatile 修饰 instance 后,在写操作后插入写屏障(Store Barrier),读操作前插入读屏障(Load Barrier),从而禁止这类危险重排——这是它在 DCL(Double-Checked Locking)中起作用的关键原因。
volatile 不保证原子性:复合操作仍会出错
对 volatile 变量的单次读或单次写是原子的,但像 count++、flag = !flag 这类操作包含“读-改-写”多个步骤,volatile 无法保证整个过程不可中断。
例如:
- 两个线程同时执行 volatile int count = 0; count++;
- 都读到 0 → 都加 1 → 都写回 1;
- 最终结果是 1,而非预期的 2 —— 发生了丢失更新。
这种场景必须用 synchronized、Lock 或 AtomicInteger 等真正支持原子操作的机制。
常见误用与边界注意
volatile 不是万能锁,以下情况它帮不上忙:
- 用 volatile Boolean 替代 boolean:自动拆箱可能空指针,且 null 无同步语义;
- volatile List
list:只保证 list 引用地址变更可见,add("x") 的元素内容不自动可见; - if (running && count > 0):running 可见,但 count 不可见,条件判断整体仍不可靠;
- 多个 volatile 字段组合判断状态:它们之间无 happens-before 关系,无法协同建模复杂状态。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











