volatile解决线程间变量不可见和指令重排序问题,保证可见性与禁止重排序,但不保证原子性;如volatile int count的count++仍非原子操作,需用AtomicInteger等替代。
核心作用就两条:保证可见性 和 禁止指令重排序。它不解决原子性——这点必须先划重点。比如 volatile int count,count++ 仍然是非原子操作,会出错。
典型现象是“死循环卡住”:一个线程把 flag = true,另一个线程还在 while(!flag) 里转圈,永远不退出。这不是代码逻辑错,而是 JVM 和 CPU 的缓存优化导致的——线程各自读写自己的 CPU 缓存,没及时同步到主内存。
volatile 变量:写操作强制刷回主内存;读操作强制从主内存加载最新值适用场景非常明确,不是所有共享变量都该加 volatile。滥用反而掩盖真正需要同步的地方。
volatile boolean isRunning、volatile boolean shutdownRequested,只做开关控制,无复合操作volatile 前写普通变量,能保证该普通变量对后续读该 volatile 的线程可见反例:不要用它保护计数器、累加器、集合操作等——这些必须用 AtomicInteger 或 synchronized。
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
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 的新值ready = true,再写 data = 42,线程 B 就可能读到 ready == true 但 data == 0
关键字就万事大吉”最常被低估的,是它只管“这个变量”,不管“这个变量周围的代码”。一旦涉及多个变量协作或复杂状态流转,就得上更重的同步机制。