volatile仅保证可见性和有限有序性,不保证原子性;synchronized则同时保证可见性、原子性和有序性,适用于临界区保护和多变量协同场景。

volatile 怎么让线程看到最新值
它不加锁,但强制每次读都从主内存取,每次写都立刻刷回主内存。JVM 禁止对 volatile 变量的普通读写重排序,也禁止将其与前后指令重排(有 happens-before 语义)。
常见错误现象:多个线程轮询一个标志位(如 running = false),但没加 volatile,导致某个线程永远看不到修改,死循环卡住。
- 仅适用于「单次读/写原子性」场景:boolean、int、long(64位 JVM 上 long 读写是原子的)、引用类型赋值
- 不保证复合操作原子性:比如
counter++(读-改-写三步)即使counter是volatile,依然会出错 - 不能替代
synchronized做临界区保护,比如需要同时更新两个变量时,volatile无法保证二者状态一致
volatile 和 synchronized 的关键区别在哪
synchronized 既保证可见性,也保证原子性和有序性;volatile 只保可见性和有序性(且是有限制的有序性)。
使用场景差异明显:
立即学习“Java免费学习笔记(深入)”;
- 用
volatile:状态标志(isShutdown)、一次性安全发布(如双重检查锁里的 instance)、与 LockSupport 配合的简单信号通知 - 用
synchronized:需要互斥访问共享资源、涉及多个变量协同、或需等待/通知机制(wait/notify) - 性能上,
volatile无上下文切换和锁竞争开销,但每次读写都有内存屏障成本;synchronized在无竞争时已很轻量(偏向锁、轻量级锁优化),但高竞争下代价明显
为什么 volatile 不能防止指令重排序
它本身不“防止”重排序,而是通过插入内存屏障(Memory Barrier)来约束重排序范围。JVM 在编译和运行时仍可能重排,但必须满足屏障的语义限制。
例如:volatile 写之前的所有普通写,不能被重排到该写之后;volatile 读之后的所有普通读,不能被重排到该读之前。
- JSR-133 定义了 4 种屏障:LoadLoad、StoreStore、LoadStore、StoreLoad ——
volatile读/写各对应不同组合 - 注意:这些屏障只对当前变量生效,不影响其他非
volatile变量之间的重排顺序 - 典型坑:以为给一个 flag 加了
volatile,就能保证它前面初始化对象的操作一定对其它线程可见 —— 实际上,若对象字段没用volatile或同步保护,仍可能看到半初始化状态(需配合安全发布模式)
Java 9+ 中 VarHandle 对 volatile 的替代价值
VarHandle 提供更细粒度的内存访问控制,比如可以用 getVolatile() / setVolatile() 模拟 volatile 字段语义,还能对数组元素、堆外内存做类似操作。
但它不是“取代”,而是补充:
- 动态获取字段句柄(反射式),适合泛型工具类或框架层封装
- 支持更多访问模式:如
getAcquire()/setRelease()(比 volatile 更弱的语义,开销略低) - 普通业务代码没必要替换 —— 直接用
volatile字段更清晰、JIT 更友好;只有在需要运行时决定访问目标或绕过字段限制时才考虑VarHandle
真正容易被忽略的是:volatile 修饰的 long/double 在 32 位 JVM 上仍存在非原子读写风险(虽然现在基本没人用了),以及它完全不管 CPU 缓存一致性协议(MESI)底层细节——你看到的“立即可见”,其实是 JVM + 硬件共同协作的结果,不是魔法。









