volatile 能保证什么?

考查重点:可见性、有序性与原子性。volatile 保可见性和有序性,i++ 仍非原子;复合更新用锁或原子类

本题目录

参考答案

核心回答

Java 的 volatile 主要保证共享字段读写的可见性,以及 Java 内存模型要求的有序性。它不提供互斥,不能把 i++、先检查再修改等复合操作变成一个原子操作。以下依据 Java SE 25 规范说明。

  • 可见性:对一个 volatile 字段的写,happens-before 于同步顺序中后续对同一字段的读。结合程序顺序和传递性,写线程在这次发布之前的普通写,也能对读线程随后执行的读取可见。
  • 有序性:编译器和处理器不能通过重排序破坏上述同步效果。它约束的是相关操作的可观察行为,并非让所有代码都禁止重排,也不是把两个线程强制排成先后执行。
  • 原子性边界:单次 volatile 读或写是原子的,包括 volatile long 和 volatile double;但“读、计算、写”组合起来仍可被其他线程穿插。

所以它适合状态标志和安全发布等场景。多个线程同时累加计数,可以用 AtomicInteger.incrementAndGet();若要一起维护多个字段的业务约束,通常需要锁或其他完整的同步方案。

进一步解释

为什么可见性仍解决不了 i++?

假设计数器当前为 0,两个线程都执行自增。它们可能先后读到 0,各自在本地算出 1,然后分别写回 1。每次读写都符合 volatile 规则,最后仍只增加了 1。

问题是两次自增的步骤发生了交错,不是“另一个线程没有及时刷新缓存”。需要保证整次自增不可分割,而不只是分别保证读取和写入。类似地,把余额声明为 volatile,也不能让“余额足够就扣款”自动成为原子操作。

volatile 怎样连带发布普通数据?

以一次性初始化为例:ready 初始为 false,唯一的发布线程先准备好普通字段中的数据,再把 volatile ready 置为 true。读线程读到这个 true 后,才去读取那些数据;在没有后续并发改写的前提下,就能看到发布前完成的初始化。

原因是:准备数据先于写 ready,写 ready 又先于相应的读,读线程再读取数据,通过 happens-before 的传递性把这些操作连接起来。

关键是读写同一个 volatile 字段,并遵守先准备、后发布、先观察发布、后使用数据的顺序。写一个字段、读另一个无关字段,不能仅凭“它们都有 volatile”建立这种关系。

底层是不是每次都直接读写主内存?

这只是容易误导的简化说法。JMM 定义的是程序允许观察到的结果;JVM 会结合目标处理器的内存模型、内存屏障和缓存一致性机制实现这些保证。具体指令和屏障能否合并、省略,取决于 JVM、处理器和优化结果,不能把某种架构的实现写成 Java 的统一指令规则。

volatile 也不提供线程调度或通知机制。用它作停止标志,工作线程仍要继续执行并再次检查标志;它不会自动唤醒一个阻塞中的线程。

常见追问

引用是 volatile,对象里的字段也都线程安全吗?

不会。通过 volatile 引用发布一个已经初始化好的对象,可以让读到该引用的线程看到发布前的初始化结果;发布后多个线程继续修改对象内部的普通字段,仍需要合适的同步。volatile 数组引用也不会让每个数组元素自动具有 volatile 读写语义。常见做法是发布不可变配置快照,更新时整体替换引用。

双重检查锁的单例为什么还需要 volatile?

外层检查和快速返回没有持有初始化使用的锁,实例引用需要安全发布,避免另一个线程看到非空引用却看不到完整初始化结果。volatile 解决发布可见性和顺序,锁与内层检查负责协调多个初始化线程;只加 volatile 仍不能让“检查为空再创建”成为原子操作。

参考资料

相关面经

5 篇

水滴公司

AI全栈工程师
校招秋招一面
查看原面经 →
Java 里 volatile 关键字是什么意思?

阿里巴巴

AI 应用开发工程师
秋招一面
查看原面经 →
volatile 能保证什么?

去哪儿

AI 应用开发工程师
秋招
查看原面经 →
Java中Volatile关键字的作用是什么?它是怎么实现这些作用的?

万兴科技

Android 客户端开发工程师(AI 方向)
实习一面
查看原面经 →
volatile关键字:保证什么?不保证什么?

百度

Agent 应用开发工程师
秋招一面、二面、三面
查看原面经 →
volatile有什么作用

相关题目

4 题