volatile关键字
- 用来修饰可在不同线程访问和修改的变量
- 无法修饰方法、代码块、方法参数、局部变量、常量
CPU高速缓存
- 程序运行时数据是存储在内存中的,而程序的执行工作由CPU来完成
- CPU的发展是遵循摩尔定律的(即价格不变,集成电路可容纳元器件数目每隔18-24个月便会增加一倍,性能也将提升一倍),因此CPU的性能会越来越好。若每次都先去内存读取数据,程序执行效率并不会提升,因此厂商为解决这一问题,引入了CPU高速缓存功能
- 即在CPU中缓存一份内存数据,频繁获取时无需去内存获取,大大提高CPU效率。当需修改数据时可先修改CPU缓存中数据,等运算结束后再回写至内存中。这就会引入新的问题
- 当前设备大都是多核CPU(即CPU存在多个运算单元),在单线程场景中使用CPU高速缓存无任何问题,但程序大都需要在多线程场景中运行,所以就引入了可见性问题(即线程B中修改变量a,对线程A不会立即可见)
可见性
volatile保证了不同线程对共享变量操作时的可见性,即一个线程修改,其他线程立即可见
- 未使用volatile修饰
- 在线程2中修改线程1终止条件,线程1不会立即可见(即终止线程1),而会读取其在CPU缓存的原值
- 使用volatile修饰变量
- 指示JVM,该共享变量不稳定,每次使用直接读取主存中值
原理
- 生成底层汇编指令时,会对volatile修饰共享变量增加Lock前缀指令,Lock前缀指令会锁定当前内存区域(缓存行)并将当前缓存行数据立即回写至主内存
- CPU缓存回写到内存时会通过MESI协议(Modified,Exclusive,Shared,Invalid高速缓存一致性协议)向其他CPU广播一条消息使其他缓存了该变量的地址失效,使用时需重新到内存中获取
MESI协议
- Modified:缓存行已被修改,还未写入主内存,此时只能有一个CPU独占该修改状态
- Exclusive:缓存行与主内存一致,且为主内存唯一拷贝,此时只能有一个CPU独占该状态
- Shared:此高速缓存行可能存储在计算机的其他高速缓存中,且与主内存一致,此时各CPU均可对该数据读取但不能写入
- Invalid:缓存行失效,不能使用
有序性
程序执行的顺序要按照代码的先后顺序
- 系统为充分利用缓存,提高程序执行速度,编译器在底层执行时会进行指令重排序操作,引入"有序性"问题
- volatile通过禁止编译器、CPU指令重排序和部分happens-before规则,解决有序性问题
原理
- 内存屏障(内存栅栏,是一个CPU指令)
声明为volatile后,变量进行读写操作时,会通过插入特定的"内存屏障"方式禁用指令重排序
写内存屏障
- volatile变量在进行写操作时,会在变量的前后插入StoreStore屏障,确保本次写操作前所有普通写操作均已完成;接着在写操作后插入StoreLoad屏障,强制所有后来读写操作在本次操作后进行,既保证了其他线程对变量立即可见
读内存屏障
- volatile变量在进行读操作时,会在读操作前插入LoadLoad屏障,确保本次读操作前所有读操作均已完成;接着在读操作后插入LoadStore屏障,防止本次读操作后的写操作被重排序到读之前,即保证了读取时总是最新的写入值
StoreStore:禁止之前的普通写和之后的volatile写重排序
StoreLoad:禁止之前的volatile写和之后的volatile读/写重排序
LoadLoad:禁止之后所有的普通读操作和之前的volatile读重排序
LoadStore:禁止之后所有普通写操作和之前volatile读重排序
原子性
对volatile变量单次读/写操作可以保证原子性,如double和long类型变量,但不能保证i++这种操作的原子性,i++本质上是读、写两次操作