要深入理解volatile,必须了解JMM(Java Memory Model)。JMM定义了线程与主内存的抽象关系:每个线程拥有独立的工作内存,存储了主内存中变量的副本。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
3.1 内存屏障 (Memory Barrier)
volatile关键字的实现依赖于JVM底层的内存屏障指令。在写volatile变量时,JVM会插入StoreStore和StoreLoad屏障;在读volatile变量时,会插入LoadLoad和LoadStore屏障。这些屏障禁止特定的指令重排,从而保证了有序性。
3.2 Happens-Before 原则
在Java内存模型中,volatile写规则规定:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。这意味着,写操作之前的所有变量赋值,对读该volatile变量的线程都可见。
JMM模型将内存分为主内存和工作内存。线程A修改变量后,通过unlock操作将变量刷新到主内存;线程B通过lock操作从主内存加载变量到工作内存。volatile关键字强制了这两个步骤的即时性,消除了缓存不一致的问题。
- 主内存:共享变量的唯一真实存储地
- 工作内存:线程私有的数据存储区
- 交互规则:8种操作(lock, unlock, read, load, use, assign, store, write)
内存屏障是CPU级别的指令,JVM通过它们来禁止指令重排。主要包含四种屏障:
- LoadLoad屏障:对于任意两个Load操作,前一个Load一定早于后一个执行完
- StoreStore屏障:对于任意两个Store操作,前一个一定早于后一个执行完
- LoadStore屏障:对于任意Load和Store操作,Load一定早于Store执行完
- StoreLoad屏障:对于任意Store和Load操作,Store一定早于Load执行完(开销最大)
Happens-Before原则是判断数据是否存在竞争、线程是否安全的主要依据。volatile相关的规则有两条:
- volatile变量规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。
- 传递性:如果A happens-before B,且B happens-before C,则A happens-before C。
这意味着,volatile写之前的所有代码,对读该volatile变量的线程都可见,实现了“半同步”的效果。