volatile 翻译:不稳定的。
它时一种比synchronized关键字更轻量级的同步机制,访问volatile变量时,不会执行加锁操作。
用在哪?
volatile关键字可以修饰在类变量或者实例变量上,不能修饰在方法参数,局部变量,实例常量以及类常量上。
volatile的两个作用
1、保证可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就说明:保证了不同线程对这个变量进行操作时的可见性,即线程修改了某个变量的值,这线程对其他线程来说是立即可见的。
我们先说一下不用volatile关键字修饰的变量:通过上图我们可以看到,每个线程都有自己的工作内存。共享变量放在主内存里,当线程需要对共享变量进行操作时,就会将变量副本放到工作内存中。然后线程去自己的工作内存操作副本,然后在某一时刻去修正主内存中的共享变量,这个时刻对普通变量是没有规定的。
这这样会出现什么问题?
当我们在高并发的情况下,线程A将共享变量读取到工作内存进行操作后,没有立即返回给主内存,由于是不可见的,所以线程B不知道有线程正在操作该变量所以就会去主内存读取,等到线程B读取共享变量之后,线程A将修改后的共享变量写回主内存。这时就会造成B读取的变量与主内存中的变量不一致,如果这时B用当前工作内存中的值去操作就会有问题。
为什么volatile修饰之后就会立即可见?
在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令。我们想这个Lock指令肯定有神奇的地方,那么Lock前缀的指令在多核处理器下回发生什么?
主要有这两个方面的影响:
1.将当前处理器缓存行的数据写回系统内存
2.这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
上面这两句话是什么意思?
通俗讲:因为volatile原语操作保证该过程不被打断,也就是说当线程a在修改变量之后会立即写回系统内存。为了保证各个处理器的缓存是一致的,就会出现缓存一致性协议。
缓存一致性协议:最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。
它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
2、禁止指令重排优化
指令重排序优化:普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序和程序代码中的执行顺序一致。这句话就是说程序在执行过程中计算机为了优化执行的速度会重新对代码进行排序,但是计算的结果跟顺序执行的结果是一样的。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量的读操作或者写操作的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
举个简单的例子:
//x、y为非volatile变量
//flag为volatile变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
那么我们回到前面举的一个例子:
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
这个例子有可能语句2会在语句1之前执行,那么就可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。
这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。
volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。
通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中