在 java 中,volatile 是一个轻量级的同步机制关键字,用于修饰字段(变量),其核心作用是:
保证变量的“可见性”和“禁止指令重排序”,但不保证原子性。
下面我们从原理层面详细解析 volatile 的工作机制。
一、三大特性:可见性、有序性、原子性
| 特性 | volatile 是否支持 | 说明 |
|---|---|---|
| 可见性(visibility) | ✅ 支持 | 一个线程修改了 volatile 变量,其他线程能立即看到最新值 |
| 有序性(ordering) | ✅ 支持(禁止重排序) | jvm 和 cpu 不会对 volatile 读写进行重排序优化 |
| 原子性(atomicity) | ❌ 不支持(除 long/double 的简单读写) | 如 i++ 这类复合操作不是原子的 |
二、volatile 的底层原理(基于内存模型)
1.java 内存模型(jmm)背景
- 每个线程有自己的工作内存(缓存、寄存器)
- 所有变量存储在主内存中
- 线程对变量的操作必须先从主内存拷贝到工作内存,操作后再写回
问题:如果没有同步机制,线程 a 修改了变量,线程 b 可能永远看不到新值(因为读的是本地缓存)。
2.volatile 如何解决可见性?
当一个字段被声明为 volatile:
- 写操作:线程必须将该变量的最新值立即刷新到主内存
- 读操作:线程必须从主内存重新读取该变量的值,而不是使用本地缓存
这相当于每次读写都强制与主内存同步。
3.内存屏障(memory barrier / memory fence)
jvm 在编译 volatile 读写时,会插入内存屏障指令,实现两个效果:
(1)禁止指令重排序
- 在 volatile 写之前的操作,不能重排到写之后
- 在 volatile 读之后的操作,不能重排到读之前
例如:
// 假设 flag 是 volatile a = 1; // 普通写 flag = true; // volatile 写
→ a = 1 一定发生在 flag = true 之前,不会被重排序。
(2)强制刷新缓存
- 写屏障(store barrier):确保写入主内存
- 读屏障(load barrier):确保从主内存加载
这些屏障由 jvm 根据不同 cpu 架构(x86、arm 等)生成对应的底层指令(如 lock 前缀指令)。
三、典型应用场景
✅ 场景 1:状态标志位(最常见)
public class taskrunner {
private volatile boolean running = true;
public void stop() {
running = false; // 其他线程能立即看到
}
public void run() {
while (running) {
// do work
}
}
}若不用 volatile,run() 方法可能因读取本地缓存而永远无法退出。
✅ 场景 2:单例模式中的双重检查锁定(dcl)
public class singleton {
private static volatile singleton instance;
public static singleton getinstance() {
if (instance == null) {
synchronized (singleton.class) {
if (instance == null) {
instance = new singleton(); // 防止重排序导致未初始化完成就被引用
}
}
}
return instance;
}
}如果没有
volatile,new singleton()的三步(分配内存、初始化、赋值)可能被重排序为“分配 → 赋值 → 初始化”,导致其他线程拿到未初始化的对象。
❌ 不适用场景:复合操作(非原子)
private volatile int count = 0;
public void increment() {
count++; // 实际是:读取 count → +1 → 写回,非原子!
}此时应使用 atomicinteger 或加锁。
四、与 synchronized 的区别
| 特性 | volatile | synchronized |
|---|---|---|
| 保证可见性 | ✅ | ✅ |
| 保证原子性 | ❌(除简单读写) | ✅ |
| 保证有序性 | ✅(禁止重排序) | ✅(隐式包含) |
| 性能开销 | 低(无锁) | 较高(涉及 monitor 锁) |
| 适用范围 | 仅变量 | 方法/代码块/变量 |
volatile是 synchronized 的轻量替代方案,但功能更弱。
五、补充:long 和 double 的特殊性
java 规范规定:
- 对 非 volatile 的 long/double,读写可能被拆分为两个 32 位操作(非原子)
- 对 volatile 的 long/double,jvm 保证读写是原子的(64 位一次性操作)
总结
volatile 的核心原理是:
- 通过内存屏障实现变量的可见性;
- 禁止编译器和处理器重排序,保证有序性;
- 不提供原子性,不能用于复合操作。
✅ 适用条件:
当一个变量被多个线程访问,且满足:
- 写操作不依赖当前值(如
flag = true)- 或者只有一个线程写,其他线程只读
否则,请使用 synchronized、reentrantlock 或 java.util.concurrent.atomic 包中的原子类。
如需进一步了解内存屏障或 jmm 模型,也可以继续提问!
到此这篇关于java中volatile关键字解释说明的文章就介绍到这了,更多相关java volatile关键字内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论