volatile是Java最被误解的关键字。面试官用它考JMM(Java内存模型)——很多人以为volatile=原子,这是错的还会导致线上bug。
两个保证:(1)可见性——volatile写对其他线程的读立即可见;(2)有序性——JVM不会把volatile读写和周围内存操作重排到违反happens-before。
不保证:复合操作的原子性。`volatile int i; i++;`仍不线程安全——i++是read-modify-write三步。
实现:x86上volatile写会发一个StoreLoad屏障(lock前缀指令),把store buffer刷到主存,使其他核的缓存失效。
经典用例:DCL双重检查锁单例——instance必须volatile防止半初始化发布(另一个线程看到非null但对象没构造完)。
什么时候用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术语。
单写多读的flag或状态变量;发布完全初始化的对象引用;跨线程关停信号。
x86上快很多——几乎零开销。但功能不同;synchronized保证原子,volatile不保证。
能。JMM规定volatile long/double的写是原子的(无word tearing),平台无关。