今天来分析一下volatile在并发编程里的作用。
一、java内存模型的相关概念
1、线程之间的通信机制:共享内存和消息传递。
2、java内存模型的抽象结构:线程之间共享的变量存储在主内存中,而每个线程都有一个私有的本地内存,本地内存中存储了该线程读写共享变量的副本。如下图所示:
如果线程1,2,3之间要进行通信的话,必须要经过以下两个步骤:
(1)线程1把工作内存1中更新过的共享变量刷新到主内存中。
(2)其他线程从主内存去读取最新的已更新过的共享变量。
3、举个简单的例子:
i = i + 1;
当某个线程执行到这个语句时,会先从主内存中读取i的值复制到本地内存副本里,然后CPU执行指令对i进行加1的操作,最后再把最新的i的值更新到主内存里。
这个操作在单线程里时没有问题的,但是在多线程环境下,可能会出现这样的问题。假设i的初始值为0,两个线程分别进行加1操作,预期的结果应该最终i的值为2,但是真的是这样吗?线程1从主内存里拿到i的最新值为初始值0,复制到本地内存后进行加1操作,与此同时线程2也从主内存拿到最新的i值,也是0(因为没有被更新过),复制到本地内存后进行加1操作,线程1处理完后把最新的i值1更新到主内存中,线程2也把处理完的最新i值更新到主内存里,但是主内存的i最终还是1而不是预期的2,这就是著名的缓存一致性问题,如何解决?
通常有两种方法:
(1)通过总线锁:还有处理器提高的LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,那么该处理器就可以独占内存。因为其他处理器的请求会被阻塞,所以导致CPU无法访问内存,效率非常低。
(2)通过缓存一致性协议:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
二、并发编程中的几个概念
1、原子性:类似事务的概念,一个或多个操作之间,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
比如:甲给乙转账100元钱,首先要从甲的账户减去100元,再在乙的账户上加上100元,这两个操作必须是具有原子性的,不能只有甲的账户减去了100元而乙的账户没有加上100元。
2、可见性:多个线程同时访问一个共享变量时,如果某一个线程对这个共享变量做的修改,其他线程必须可以看到这个最新的修改的值。
比如:甲的银行余额为100元,现在甲给乙转账了100元,a线程从主内存中读取余额的值为100,判断是否足够进行转账100元的操作,足够则从甲的银行账户余额减去100元,此时如果另外b线程也在操作转账操作,必须要保证这个余额的可见性,如果b线程看到的余额还是100元而不是a线程进行转账操作后的0元,就会造成重复转账的问题。
3、有序性:程序执行的顺序是按照代码的先后顺序执行。但是处理器有时候会为了效率,进行指令重排序操作。
举个栗子:
int a = 0;
int b = 0;
a = 1; //语句1
b = 1; //语句2
按照代码的顺序,语句1应该是先于语句2执行的,但是处理器可能会进行指令重排序,让语句2先于语句1执行,不过重排序会保证程序的最终结果和代码按顺序的执行结果是相同的。在多线程环境下,必须要保证有序性,否则会对执行结果有影响。
三、volatile关键字解析
1、被volatile修饰的变量,转成汇编代码后会发现多出了Lock前缀的指令,这个指令会有两个效果:
a、将当前处理器缓存行的数据回写到系统内存中。在多处理器环境中,Lock#信号确保在声言该信号期间,处理器可以独占内存。但在现在一般处理器都不会去锁总线,毕竟开销大,而是采取缓存一致性协议,它会阻止同时修改由两个以上处理器缓存的内存区域数据。
b、写回内存的操作会使其它CPU缓存了该内存地址的数据无效。处理器使用MESI(修改、独占、共享、无效)控制协议维护内部缓存和其他处理器的缓存一致性。
这样便保证了被修饰变量的可见性。
四、volatile关键字的应用场景
volatile属于轻量级的锁,在特定的场景下可以替代synchronize关键字的效果,而且比synchronize效率高,但volatile不能完全替代synchronize关键字。
通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
1、举个应用的例子
双重检查创建单例
class Singleton{
private volatile static Singleton instance = null;
private Singleton(){}
private Singleton getInstance(){
if(null == instance){
synchronize(Singleton.calsss){
if(null == instance){
instance = new Singleton();
}
}
return instance;
}
}
}
参考资料:
《Java并发编程的艺术》
《深入理解Java虚拟机》
http://www.cnblogs.com/dolphin0520/