在多线程开发中,数据一致性与执行顺序是永恒的挑战。volatile、内存屏障和 CPU 缓存正是为此而生。
一、CPU 缓存带来的挑战
在深入 volatile 之前,得先理解 CPU 缓存。现代 CPU 为了效率,不会每次都直接读写主内存,而是优先操作高速缓存(Cache)。这带来了两个核心问题:
- 可见性问题:线程 A 修改了共享变量并写入自己的缓存,但没同步到主内存。线程 B 读取时可能从自己的缓存拿到旧值,导致双方看到的'同一个变量'不一致。
- 有序性问题:CPU 为了提升指令执行效率,可能会重排序(Reordering)。单线程下没问题,但在多线程环境下,原本依赖先后顺序的逻辑可能被打乱。
缓存命中与缺失
- 命中:数据在缓存中,速度极快。
- 缺失:数据不在缓存中,需从主内存加载,速度变慢。
典型场景:可见性失效
private static int count = 0;
public static void main(String[] args) {
new Thread(() -> { count = 1; }).start();
new Thread(() -> {
while (count == 0) {}
System.out.println("读取到 count = " + count);
}).start();
}
线程 1 修改了缓存中的 count,但未写回主内存;线程 2 一直从自己缓存读 0,陷入死循环。这就是典型的可见性问题。
二、volatile 关键字
volatile 是 Java 提供的轻量级同步机制,能解决可见性和有序性问题,但不能保证原子性。
1. 保证可见性
当线程修改 volatile 变量后,JVM 会强制将新值写回主内存,并通知其他 CPU 缓存该变量失效。其他线程再次读取时,必须从主内存重新加载最新数据。
private static volatile int count = 0;
加上 volatile 后,线程 1 修改 count=1 会立即刷新主内存,线程 2 的缓存失效,从而跳出循环。
2. 禁止指令重排序
CPU 可能对无依赖关系的指令进行重排。volatile 通过插入内存屏障,确保读写顺序严格按照代码逻辑执行。

