Java 面试题

Java volatile:可见性、有序性、为什么不是原子

"volatile关键字保证什么?不保证什么?"

为什么面试官会问这道题

volatile是Java最被误解的关键字。面试官用它考JMM(Java内存模型)——很多人以为volatile=原子,这是错的还会导致线上bug。

如何回答

  1. 1

    两个保证:(1)可见性——volatile写对其他线程的读立即可见;(2)有序性——JVM不会把volatile读写和周围内存操作重排到违反happens-before。

  2. 2

    不保证:复合操作的原子性。`volatile int i; i++;`仍不线程安全——i++是read-modify-write三步。

  3. 3

    实现:x86上volatile写会发一个StoreLoad屏障(lock前缀指令),把store buffer刷到主存,使其他核的缓存失效。

  4. 4

    经典用例:DCL双重检查锁单例——instance必须volatile防止半初始化发布(另一个线程看到非null但对象没构造完)。

  5. 5

    什么时候用AtomicXxx:需要复合更新(递增、check-and-set)。AtomicInteger.incrementAndGet()内部用CAS把操作变原子。

参考回答示例

volatile给你两个保证。第一可见性——volatile写对其他线程的读立即可见。没有volatile的话,一个线程写完值,另一个线程可能永远读到旧值,因为写还在store buffer或本地缓存里。volatile强制写刷到主存并使其他核的缓存失效。第二有序性——volatile在访问周围施加happens-before顺序。JVM和CPU不会把volatile写之前对普通字段的写重排到volatile写之后,也不会把volatile读之后的普通字段读重排到volatile读之前。这对安全发布对象至关重要。volatile不给你的是复合操作的原子性。volatile int i; i++还是bug——i++其实是读、加1、写,其他线程能在读和写之间读到同样的值。递增要用AtomicInteger,incrementAndGet()用CAS把整个操作变原子。实现上,x86上JVM用lock前缀指令实现volatile写,充当内存屏障(StoreLoad)——刷store buffer并触发一次缓存一致性。x86的volatile读几乎零开销,因为x86的内存模型本身就强。ARM这种弱内存模型屏障更重要也更贵。经典用例:DCL双重检查锁单例。instance必须volatile。没volatile的话,A线程可能看到instance非null但对象的构造函数还没跑完——因为"instance = new Foo()"的发布写可以被重排,让引用先可见而字段还没初始化。volatile防止这种重排。什么时候用volatile vs AtomicXxx vs synchronized:volatile用于简单flag/状态——"是否请求关闭"这种单写多读场景;AtomicXxx用于计数器和复合更新;synchronized用于多变量不变式或保护块(几个字段必须一起动)。

实用技巧

  • 明确说volatile不是原子——面试官爱抓i++这个误区。

  • volatile + DCL单例是经典追问,要能写出代码。

  • volatile → 内存屏障 → x86 lock前缀,是资深面试的合适深度。

  • 忘了"happens-before",即答侠可以实时提示JMM术语。

常见问题

什么时候用volatile?

单写多读的flag或状态变量;发布完全初始化的对象引用;跨线程关停信号。

volatile读比synchronized快吗?

x86上快很多——几乎零开销。但功能不同;synchronized保证原子,volatile不保证。

volatile能解决long/double撕裂吗?

能。JMM规定volatile long/double的写是原子的(无word tearing),平台无关。

面试时担心忘词?即答侠实时助你

即答侠 AI 实时监听面试对话,自动识别问题并即时生成回答建议——无感辅助,让你从容应对每一道题。

免费试用即答侠