volatile解决多线程内存可见性问题,每次读写都直连主内存,但不保证原子性和互斥;适用于状态标志等简单场景,不适用于复合操作或对象内部状态保护。

volatile 解决的是多线程下的内存可见性问题
当多个线程访问同一个 volatile 修饰的变量时,每次读取都会从主内存重新加载,每次写入都会立即刷新到主内存。它不解决原子性(比如 i++ 仍是非原子操作),也不提供互斥,但能确保“一个线程改了,其他线程马上看到最新值”。
典型误用场景:用 volatile boolean running 控制线程生命周期是安全的;但用 volatile int counter 做计数器是不安全的——因为读-改-写三步无法保证原子性。
为什么普通变量可能看不到更新?——JMM 的重排序与缓存差异
JVM 允许将变量缓存在线程本地(如 CPU 寄存器或高速缓存),编译器和处理器也可能对指令重排序。没有同步机制时,线程 A 修改了 flag = true,线程 B 可能一直读到旧值 false,哪怕 A 已执行完毕。
volatile 通过插入内存屏障(Memory Barrier)禁止两类重排序:
立即学习“Java免费学习笔记(深入)”;
- 写 volatile 变量前的所有读写,不能重排序到该写之后
- 读 volatile 变量后的所有读写,不能重排序到该读之前
这同时保证了可见性和一定程度的有序性(但不是全序)。
volatile 和 synchronized 的关键区别在哪?
两者都涉及主内存与工作内存的同步,但行为不同:
-
synchronized进入时强制读主内存,退出时强制写回,并且保证临界区内所有变量的可见性 -
volatile只对被修饰的单个变量生效,不加锁,无阻塞,但也不提供原子复合操作支持 -
synchronized能防止指令重排序(在块内),volatile只在读写该变量的邻近位置设屏障 - 性能上,
volatile通常更轻量,但适用场景窄得多
例如:volatile int status 表示状态切换可用;但 if (status == READY) doWork(); 这种“检查-执行”逻辑仍需 synchronized 或 Lock 保证原子性。
哪些情况不能靠 volatile 修复?
常见误判是以为加了 volatile 就“线程安全”了。实际以下问题它完全不管:
-
volatile long timestamp;—— 虽然 long/double 的 64 位读写在某些 JVM 上可能非原子,但volatile确保其读写原子性,这点反而是它能解决的 -
volatile List items;—— 只保证items引用本身的可见性,不保证items.add(x)操作线程安全 -
volatile int[] arr;—— 同样,只对数组引用有效,arr[0] = 1不受 volatile 保护 - 构造函数中泄露
this引用,即使字段是volatile,也阻止不了其他线程看到未初始化完成的对象
真正容易被忽略的一点:volatile 变量的初始化必须在对象完全构建完成后才对外发布,否则即使字段声明为 volatile,仍可能看到部分构造的状态。









