要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
用volatile修饰共享变量之后会发生如下变化:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
注意:是修改共享变量后,会影响另一个线程的读取。如果第一个线程只是读取共享变量,那么就不影响另一个线程去读取。这一点很重要。虽然volatile能保证可见性,但保证不了原子性。类似于i=i+1,这种非原子操作,用volatile修饰也是没用的。只有boolean类型用volatile修饰才是有用的。因为xxx=false是原子操作,不需要读,是直接写,那就影响另一个线程的读写操作。一旦有修改,volatile变量在别的线程中就要重读。当然如果两个线程同时读取到一个变量(boolean型),这个时候又各自进行,一个修改,另一个做一次判断(同时),那么修改后,只要另一个线程没有再去判断,那么这个含判断的线程用的还是之前的,这个是没办法的,除非用锁去保证。
因为volatile保证了可见性,所以不会拷贝到工作线程,而是直接从主存中获取。另外final是要去拷贝一份到其它工作线程中的,所以两者是冲突的,所以volatile和final不能一同使用。
volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
举个例子:
//x、y为非volatile变量
//flag为volatile变量
x =
2
;
//语句1
y =
0
;
//语句2
flag =
true
;
//语句3
x =
4
;
//语句4
y = -
1
;
//语句5
//线程1:
context = loadContext();
//语句1
inited =
true
;
//语句2
//线程2:
while
(!inited ){
sleep()
}
doSomethingwithconfig(context);
如果inited没有volatile修饰,那么有可能语句2会在语句1之前执行,那么久可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。
-----------------------------------------------
volatile的应用场景有两个:
1、就是
//线程1:
context = loadContext();
//语句1
inited =
true
;
//语句2
//线程2:
while
(!inited ){
sleep()
}
doSomethingwithconfig(context);这个例子
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;
}
}
---------------------------------------------------
- public class Singleton {
- static class SingletonHolder {
- static Singleton instance = new Singleton();
- }
- public static Singleton getInstance(){
- return SingletonHolder.instance;
- }
- }