一个变量被volatile修饰时,它会保证需要写回的值会被立即更新到内存,而读取时也会直接从内存读取新值。
volatile关键字保证了可见性和一定的有序性,而不保证原子性。
保证可见性
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
这段代码通过线程2来结束线程1的循环,但会存在一些问题,如果线程2把stop的值修改后并没有及时写回,而去做其他事情了。这时候线程1并不知道线程2将stop修改了而会一直循环下去。
但使用了volatile关键字修饰stop后就不会有这种问题,因为线程2对stop的修改会立刻反应到内存上,而线程1对stop的读取也会直接从内存上。这就保证了可见性。
不保证原子性
可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
//线程1
inc++;
//线程2
inc++;
假设 i 是volatile修饰的,存在以下情况:
/**
*线程1读取inc的值后,还没有操作就被阻塞了。
*线程2被唤醒,从主存读取inc的值,加1,然后被阻塞。(此时还没来得及把新的值重新赋值给inc,当然也还没同步到主存)
*线程1被唤醒,inc值加1,然后同步到主存(线程1结束,使得线程2缓存行无效)
*线程2被唤醒,把最新的值赋值给inc,同步到主存(此时线程2,inc的值在第2步时已经被处理过了,仅仅只是把新的值赋值给inc而已。这个时候是不会再去读取inc的缓存行的,虽然inc的缓存行此时已经无效了)
*这时候得到的最后结果 i 仍然是1。
**/
也就是说volatile只保证写回操作是直接写回到内存中并让其他线程关于这个变量的缓存失效,然后让其他线程读取这个变量时知道这个变量缓存失效了,需要重新去内存拿。但是对于其他线程已经对这个变量完成操作只是还没来得及写回的情况无能为力。
再深究一点,如果是非volatile修饰的变量,其执行步骤其实有5步:①内存读取到缓存 → ②读取缓存 → ③进行加1 → ④写入缓存 → ⑤缓存写入内存 。但volatile 只影响了①→②(使得直接从内存读取)、③(使得如果加1时判断内存失效则重新从内存读取)、④→⑤(使得直接写回到内存),而无法影响③→④(已经执行过加1,只是等待写回)。
保证一定的有序性
volatile关键字禁止指令重排序其实有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把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必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中 。也就是必须现有的条件已经能保证原子性的情况下,才能使用volatile关键字。