本文目录导读:

你理解得非常准确。volatile 保证可见性,但不保证原子性,这是 Java 并发编程中的一个核心概念。
为了帮你更透彻地理解,我把这两个特性拆开解释,并给出一个典型的“原子性失效”的例子。
volatile 保证了什么?
保证可见性
- 含义:当一条线程修改了一个
volatile变量的值,新值对于其他线程来说是立即可见的。 - 原理:Java 内存模型(JMM)规定,对
volatile变量的写操作,会强制将线程工作内存中的变量值刷新到主内存;而对volatile变量的读操作,会强制从主内存中读取最新值,这样就避免了线程间“看不见对方修改”的问题。 - 场景:适合用于状态标记位(
boolean flag)。
volatile 不保证什么?
不保证原子性
-
含义:对于
count++这种复合操作(读取-修改-写入),volatile无法保证整个操作不被其他线程打断。 -
本质原因:
count++在字节码层面至少是三个指令:getfield(读取 count 的值到栈顶)iadd(将 count 加 1)putfield(将新值写回 count)
即使
count是volatile的,线程 A 执行完getfield和iadd后,在即将执行putfield时,线程 B 可能已经修改了count的值,线程 A 最终写回的值是基于它之前读到的旧值,从而导致线程 B 的更新被覆盖,这就是典型的丢失更新问题。
一个经典的反例:volatile 无法实现计数器
下面这段代码,虽然 count 是 volatile 的,但最终结果几乎不可能是 20000(因为两个线程各累加 10000 次)。
public class VolatileAtomicDemo {
// volatile 保证可见性,但不保证原子性
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++; // 这不是原子操作!
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++; // 这不是原子操作!
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count 的最终结果:" + count);
// 预期:20000,实际:通常是一个小于 20000 的随机数(如 19876, 19923 等)
}
}
既然 volatile 不保证原子性,那怎么办?
-
使用
synchronized关键字:加锁可以保证原子性(代码块内的操作不可分割)。public synchronized static void increment() { count++; } -
使用
java.util.concurrent.atomic包下的原子类:AtomicInteger,它底层通过 CAS(Compare-And-Swap)机制保证了原子性,性能通常比synchronized好。private static AtomicInteger atomicCount = new AtomicInteger(0); // 使用时 atomicCount.incrementAndGet();
总结对比
| 特性 | volatile | synchronized / AtomicInteger |
|---|---|---|
| 可见性 | 保证 (强制刷新主内存) | 保证 (释放锁时刷新主内存) |
| 原子性 | 不保证 | 保证 |
| 适用场景 | 单一变量的读写(如状态标记 flag) |
需要复合操作(如 i++)、互斥访问 |
| 性能开销 | 较低 (无锁,但有内存屏障) | 较高 (锁竞争 / CAS 自旋) |
一句话记住:volatile 适合做开关(boolean running = true),不适合做计数器(int i++)。