1 volatile简介
volatile是java的一个修饰符,volatile修饰的变量能在多线程环境下被所有的线程同步获取和一致性更新, 具有可见性和禁止指令重排两个特性。
可见性:当一个线程修改一个共享变量时, 另一个线程能读到这个修改的值。
禁止指令重排:jvm在编译的时会自动优化,可能会将代码的顺序调整导致错误,被volatile修饰的词, 就能保证在编译的时候, 前后的顺序是不变的。
2 volatile原理
volatile修饰的变量在编译时会被lock修饰,lock指令在操作系统中会有以下两个作用:
- Lock前缀指令会引起处理器缓存回写到内存;
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
java代码如下:
instance = new Singleton();
转变成汇编代码:
可见性原理:volatile修饰的变量在写的时候,jvm会向处理器发送一条lock前缀的指令,这个变量所在的缓存行的数据会写到系统内存。每个处理器会通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,若被修改,则将当前值设置为无效, 当处理器对这个值进行修改操作时, 重新从系统内存中吧数据读取到处理器缓存中。
禁止指令重排原理:在JVM编译期会为volatile修饰的变量的读写,加上4种内存屏障,对于内存屏障我们有loadload,storetore,loadstore和storeload 四种内存屏障,对于volatile变量,写前我们加入storetore屏障,volatile写之后加入storeload的屏障,volatile读后边加入loadload屏障,然后在后边加入loadstore屏障。这4种屏障的加入,能够保证我们volatile对同一个变量的写操作是有序的,对同一个变量的写读操作,读写操作是有序的,对同一个变量的读读操作也是有序的。
不保证原子性:volatile关键词只能保证变量的单一的读操作和写操作的原子性,换句话说,volatile关键词只能保证单一JVM操作指令的原子性。
对于i++而言,其中包含了3条JVM指令码:读取i的值 - iload;为i的值+1 - iadd;将i进行从新赋值 - istore。volatile不能保证3条JVM指令操作的原子性,因此不能保证i++操作的原子性。
volatile只能保证单一JVM操作指令的原子性。比如对i变量进行赋值操作,只对应JVM的istore指令;对i变量的读取操作,只对应JVM的iload指令,这种场景是可以保证原子性的。包括双重检查锁中,之所以对变量进行volatile的修饰,就是为了保证对象创建的操作是原子性的。通过new关键字创建Java对象,对应的JVM的指令码只有一个 - new指令。