volatile只保证对象引用的可见性、有序性,不保证对象内部操作的线程安全;它确保引用赋值与读取及时同步且不重排序,但对对象字段或方法调用无保护作用。

volatile 修饰对象引用,不是让对象本身“变易”,而是让这个引用变量的读写操作具备可见性和有序性。也就是说,它管的是“指向谁”这件事——谁改了引用、谁读到了新指向,必须及时同步、不能乱序,但不管这个被指向的对象内部字段是否安全。
volatile 修饰的是引用,不是对象内容
Java 中的引用变量本质上是一个内存地址。当用 volatile 修饰一个对象引用(比如 private volatile List
- 对 list 这个变量本身的赋值(list = new ArrayList())会立即刷入主内存,其他线程读取时一定能拿到最新地址;
- 读取 list 的操作(如 if (list != null))一定会从主内存重新加载,不会用缓存旧值;
- 编译器和 CPU 不会对该引用的读/写指令做重排序(例如:禁止把 list = new ArrayList() 和后续对 list 元素的初始化操作调换顺序)。
但它完全不保护 list 对象内部状态。比如:list.add("item") 是非原子操作,且没有 volatile 语义,多个线程同时调用可能引发 ConcurrentModificationException 或数据丢失。
典型正确用法:安全发布与状态切换
volatile 引用最常用于两种场景:
- 单例对象的安全初始化:双重检查锁中用 volatile 修饰 instance 引用,确保其他线程看到的不是半初始化的对象(防止指令重排导致构造函数未执行完就暴露引用);
- 状态标志或配置切换:如 private volatile ExecutorService executor;,主线程替换 executor 后,工作线程能立刻感知并使用新实例,但 executor 内部任务调度逻辑仍需自身线程安全。
常见误区:以为加了 volatile 就线程安全
这是最容易踩的坑。举个反例:
-
private volatile Map
cache = new HashMap(); —— 引用是 volatile 的,但 cache.put("k", 1) 依然不是线程安全操作; - private volatile AtomicInteger counter = new AtomicInteger(0); —— 这没问题,因为 AtomicInteger 本身已内置原子性,volatile 在这里多余,但也不影响;
- private volatile StringBuilder sb = new StringBuilder(); —— 危险!sb.append() 非线程安全,volatile 完全不兜底。
一句话总结:volatile 只给“引用”上锁,不给“对象里面”上锁。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











