volatile 能保证原子性吗?
更新时间:2026-09-20。问题驱动短答页。这是 Java 并发面试与实战的高频混淆点。volatile 的完整机制见 volatile 与 Java 内存模型,锁与 CAS 见 JUC 锁与 AQS。
一句话回答
不能。volatile 只保证「可见性」和「有序性(禁止重排)」,不保证「原子性」。 对 volatile int i 做 i++ 仍然是「读 → 加一 → 写回」三步,多线程交错会丢更新。要原子性必须用 AtomicInteger/CAS 或 synchronized/Lock。
JMM 三大特性对照
| 特性 | 含义 | volatile | synchronized/Lock | 原子类 |
|---|---|---|---|---|
| 可见性 | 一线程写,其他线程立刻看到最新值 | ✅ | ✅ | ✅ |
| 有序性 | 禁止不合规的指令重排 | ✅(内存屏障) | ✅ | ✅ |
| 原子性 | 操作不可被打断 | ❌(单次读/写除外) | ✅ | ✅(CAS 自旋) |
为什么 i++ 不安全
i++ 在字节码/机器层不是一条指令,大致是:
bash
1. 读 i 到寄存器/栈 (读)
2. 加 1 (改)
3. 写回主存 (写)两个线程同时读到 i=5,各自 +1 都写回 6,结果丢了一次自增。volatile 能让第 3 步的写入对别人可见,却无法把三步合成一个不可分割的整体。
java
private volatile int count = 0;
count++; // 多线程下仍会丢更新!
// 正确写法
private final AtomicInteger count = new AtomicInteger();
count.incrementAndGet(); // CAS 自旋,原子
// 或
synchronized void inc() { count++; }
// 高并发计数
LongAdder adder = new LongAdder(); adder.increment();volatile 到底保证什么
- 可见性:写 volatile 变量立即刷主存,读直接从主存取,线程不会一直用工作内存里的旧值(普通变量没有这个保证)。
- 有序性:通过内存屏障禁止特定重排——volatile 写之前的操作不会被排到它后面,volatile 读之后的操作不会被排到它前面。这就是**双重检查单例(DCL)**的实例字段必须 volatile 的原因:防止「对象引用已发布但构造还没完成」。
java
private static volatile Singleton instance; // 必须 volatile
public static Singleton get() {
if (instance == null) { // 第一次检查无锁
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton(); // 第二检
}
}
return instance;
}volatile 适合的场景
- 状态标志位:一个线程写、其他线程读的开关,如
running = false优雅停止线程。 - DCL 单例(如上)。
- 配置热更新后的可见性发布。
- 这些场景写操作本身是「一次赋值」(对引用/基本类型的单次写在规范里是原子的),volatile 补上可见性即可;一旦是自增、check-then-act、读改写,就得上锁或原子类。
注意特例:对非 volatile 的
long/double,JVM 规范允许 64 位读写拆成两个 32 位、理论上可能读到「撕裂值」;volatile(或 AtomicLong)使其读写原子。但即便如此long++依然不是原子操作。
常见坑
- 给计数器加 volatile 就当线程安全:
count++照样丢更新,用 AtomicInteger/LongAdder。 - 混淆可见性与原子性:能看到最新值不等于复合操作不被打断。
- DCL 单例漏写 volatile:可能拿到半初始化对象。
- 以为 volatile 等于锁:它不会让线程互斥等待,临界区里多条语句仍需 synchronized。
- 过度依赖而不理解内存语义:复杂同步优先用
java.util.concurrent里的成熟工具。
总结
volatile 只解决 JMM 的可见性(写立即对其他线程可见)和有序性(内存屏障禁止重排,DCL 单例因此必须用它),唯独不保证原子性;i++ 是读-改-写三个动作,加了 volatile 仍会在并发下丢更新,自增和 check-then-act 要用 AtomicInteger/AtomicReference 的 CAS、LongAdder 或 synchronized/Lock;volatile 真正合适的是单次赋值的状态标志与安全发布,复合操作一律交给锁或原子类,另注意非 volatile 的 64 位 long/double 读写本身也可能撕裂。