java volatile
重排序
在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,重排序它不会影响单线程的运行结果,但是对多线程会有影响。
- 编译器重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 处理器重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
volatile作用
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
-
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
-
禁止进行指令重排序。
volatile使用条件
volatile相对于synchronized稍微轻量些,因为它不会引起线程上下文的切换和调度。在某些场合它可以替代synchronized,但是又不能完全取代synchronized,只有在某些场合才能够使用volatile。
使用volatile必须满足如下两个条件:
- 对变量的写操作不依赖当前值
- 该变量没有包含在具有其他变量的不变式中
volatile使用场景
volatile经常用于两个场景:状态标记量、double check
double check:
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
volatile原理
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。lock前缀指令其实就相当于一个内存屏障。内存屏障是一组处理指令,用来实现对内存操作的顺序限制。volatile的底层就是通过内存屏障来实现的。
内存屏障会提供3个功能:
-
它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
-
它会强制将对缓存的修改操作立即写入主存;
-
如果是写操作,它会导致其他CPU中对应的缓存行无效。
原子性:
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
volatile是无法保证复合操作的原子性(如自增操作)
可见性:
是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取。
有序性:
程序执行的顺序按照代码的先后顺序执行。
Java提供volatile来保证一定的有序性。