volatile解决线程间变量不可见和指令重排序问题,保证可见性与禁止重排序,但不保证原子性;如volatile int count的count++仍非原子操作,需用AtomicInteger等替代。

volatile 解决什么问题:线程改了值,另一个线程却“看不见”
核心作用就两条:保证可见性 和 禁止指令重排序。它不解决原子性——这点必须先划重点。比如 volatile int count,count++ 仍然是非原子操作,会出错。
典型现象是“死循环卡住”:一个线程把 flag = true,另一个线程还在 while(!flag) 里转圈,永远不退出。这不是代码逻辑错,而是 JVM 和 CPU 的缓存优化导致的——线程各自读写自己的 CPU 缓存,没及时同步到主内存。
- 普通变量:修改只写入本线程工作内存(CPU 缓存),刷新主内存时机不确定
-
volatile变量:写操作强制刷回主内存;读操作强制从主内存加载最新值 - 底层靠 MESI 缓存一致性协议 + 总线嗅探实现,不是靠锁,所以轻量
什么时候必须用 volatile:状态标志、双重检查单例
适用场景非常明确,不是所有共享变量都该加 volatile。滥用反而掩盖真正需要同步的地方。
-
状态标志位:如
volatile boolean isRunning、volatile boolean shutdownRequested,只做开关控制,无复合操作 - 双重检查单例(DCL)中的 instance 引用:防止对象构造未完成就被其他线程看到(避免“半初始化”对象)
-
作为 happens-before 的锚点:配合其他操作建立内存可见性顺序,比如在写
volatile前写普通变量,能保证该普通变量对后续读该volatile的线程可见
反例:不要用它保护计数器、累加器、集合操作等——这些必须用 AtomicInteger 或 synchronized。
为什么 volatile++ 不行:原子性缺失的真实代价
volatile 保证的是“单次读或单次写”的原子性(如 boolean、int 赋值),但 count++ 是三步:read → modify → write,中间可能被其他线程打断。
int count = 0; // 非 volatile // 线程 A 执行 count++ // 1. 读 count = 0 // 2. 计算 0 + 1 = 1 // 3. 写回 count = 1 // 线程 B 同时执行 count++ // 1. 读 count = 0(此时 A 还没写回) // 2. 计算 0 + 1 = 1 // 3. 写回 count = 1 → 覆盖 A 的结果 // 最终 count = 1,而非预期的 2
- 即使加了
volatile,上面三步仍可被交叉执行 - 想安全累加,用
AtomicInteger.incrementAndGet() - 想保护一段逻辑,用
synchronized或ReentrantLock
容易忽略的坑:没有 happens-before,volatile 也救不了你
volatile 的可见性不是“全局广播”,它只对**该变量本身**生效。如果还有其他非 volatile 变量依赖它的状态,顺序没约束,照样可能出错。
例如:
private volatile boolean ready = false;
private int data = 0;
// 线程 A
data = 42; // 普通写
ready = true; // volatile 写 → 触发 happens-before 关系
// 线程 B
if (ready) { // volatile 读
System.out.println(data); // 此处能安全看到 data == 42
}
- 关键在
ready = true和data = 42的先后顺序——JVM 保证前者 happens-before 后者,才让线程 B 读到data的新值 - 但如果线程 A 先写
ready = true,再写data = 42,线程 B 就可能读到ready == true但data == 0 - 所以 volatile 必须和正确的执行顺序配合,不能只靠“加了关键字就万事大吉”
最常被低估的,是它只管“这个变量”,不管“这个变量周围的代码”。一旦涉及多个变量协作或复杂状态流转,就得上更重的同步机制。










