Volatile是JVM中非常重要的关键字,可以帮助我们保证变量的可见性并且禁止重排序。保证可见性我们可以理解,每个线程都有自己的local cache。当需要修改全局的变量时,需要先去工作内存中读取最新的数值并把数值加载到自己的本地工作内存,然后再进行修改。修改完成以后再将数值重新刷写回到工作内存中。因此Volatile关键字保证可见性其实就是在每次变量更新前后,强制去执行内存刷写。那,禁止指令重排是如何实现的呢?
JVM中提供的四类内存屏障指令
- loadload:读读,该屏障用来禁止处理器把上面的volatile读与下面的普通读重排序
- storestore:写写,该屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中
- loadstore:读写,该屏障用来禁止处理器把上面的volatile读与下面的普通写重排序
- storeload:写读,该屏障的作用是避免volatile与后面可能有的volatile读/写操作重排序
1 | public class VolatileTest { |
Java内存模型中定义的8种工作内存与主内存之间的原子操作
- read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
- load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
- use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
- assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
- store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
- write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量
由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
- lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
- unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
volatile如何禁止指令重排
volatile有关禁止指令重排的行为。
- 当第一个操作是 volatile 读时,不论第二个操作是什么,都不能重排序;这个操作保证了volatile读之后的操作不会被重排到volatile读之前
- 当第二个操作为 volatile 写时,不论第一个操作是什么,都不能重排序;这个操作保证了volatile写之前的操作不会被重排到volatile写之后
- 当第一个操作为 volatile 写时,第二个操作为 volatile 读时,不能重排序
四大内存屏障插入情况(具体见下面代码分析)
- 在每一个 volatile 写操作前面插入一个 storestore 屏障
- 在每一个 volatile 写操作后面插入一个 storeload 屏障
- 在每一个 volatile 读操作后面插入一个 loadload 屏障
- 在每一个 volatile 读操作后面插入一个 loadstore 屏障
volatile为什么不保证原子性?
volatile变量的复合操作(如i++)是不具有原子性的,原因是 i++ 操作从字节码角度来看,是分为三步的
多线程环境下,数据计算和数据赋值操作可能多次出现,即操作非原子。若数据再加载之后,若主内存中 count变量发生修改之后,由于线程工作内存中的值在此之前已经加载,从而不会对变更操作做出相应变化,即私有内存和公共内存中变量不同步,进而导致数据不一致
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的。
使用内存屏障分析下面代码
1 | public class VolatileTest { |
写操作:
在每一个volatile写操作 前面 插入一个 storestore屏障;
在每一个volatile写操作 后面 插入一个 storeload屏障
操作 | 说明 |
---|---|
i = 1 | 普通写 |
storestore屏障 | 禁止上面的普通写与下面的volatile写重排序 |
flag = true | volatile写 |
storeload屏障 | 禁止上面的volatile写与下面可能有的volatile读/写重排序 |
读操作:
在每一个volatile读操作 后面 插入一个 loadload屏障;
在每一个volatile读操作 后面 插入一个 loadstore屏障
操作 | 说明 |
---|---|
if (flag) | volatile读 |
loadload屏障 | 禁止上面的volatile读与下面的普通读重排序 |
loadstore屏障 | 禁止上面的volatile读与下面的普通写重排序 |
System.out. | 普通读 |