volatile关键字解析

仲佳俊

1、定义

volatile是java中的一个关键字,提供了一种较弱的同步机制,用来确保将变量的更新操作通知到其他线程。  

2、功能

volatile修饰变量实现了以下的功能:
1、可见性

当volatile修改变量时,当一个线程对该变量进行了修改,则会将变量由工作内存写会主内存中,并且通知其他拥有该变量的线程,已经取得变量失效。当其他线程读取该变量时,则需要重新将该变量由主存读入到工作内存中。即:当一个线程修改以后,确保其他线程都是拿到最新的值。

2、禁止指令重排序优化

代码由于编译器优化,在实际执行时可能与我们编写的顺序不同。编译器只保证程序执行结果还源码相同,而实际执行指令的顺序和代码有可能不一样。这样在多线程情况下就会导致乱序问题。volatile关键字可以从语义上解决这个问题。

3、volatile和synchronized的区别

1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。  
2)volatile仅能使用在变量级别,synchronized则可以使用在变量、方法。  
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性。  
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。  

4、volatile实现原理

1、可见性实现

当用volatile修饰一个变量时,有以下的两个语义:
1)修改volatile变量时会强制将修改后的值刷新的主内存中  
2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效  
以上两点确保共享变量在修改以后其他所有线程可见。

2、有序性实现

java内存模型中先行发生(happen-before)原则  
    1)同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
    2)监视器上的解锁操作 happen-before 其后续的加锁操作。
    3)对volatile变量的写操作 happen-before 后续的读操作。
    4)线程的start() 方法 happen-before 该线程所有的后续操作。
    5)线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。
    6)如果 a happen-before b,b happen-before c,则a happen-before c。
以上原则可以用于判断数据是否存在竞争、线程是否安全。

3、内存屏障

volatile的可见性和顺序性实现,JVM底层通过“内存屏障”来完成。“内存屏障”是一组处理器指令,用于控制特定条件下的重排序和内存可见性。java编译器会根据内存屏障的规则禁止重排序。  

1)内存屏障分类

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。  
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。  
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。  
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。  

2)Java中使用内存屏障的规则
java中volatile禁止重排序的规则

5、volatile案例参考

在ConcurrentHashMap的源码中,对volatile的使用可以作为参考;

6、使用volatile的条件

(1)对变量的写操作不依赖于当前值。
(2)该变量没有包含在具有其他变量的不变式中。 volatile关键字要参考以上的条件,应当审慎使用。