前言
volatile变量是Java提供的一种削弱的同步机制,用来确保将变量的更新操作通知到其他线程。我将在这篇博客中分享自己对volatile变量的理解。
volatile特点
- 保证被修饰的变量不会被重排序
- 保证被修饰变量可见性
什么是重排序
重排序是指当操作之间不存在依赖关系(Happens-Before关系)时,编译器在不改变单线程程序最终执行结果的前提下,对指令进行重排序以达到提高执行效率的目的。如下面代码中所示:
int a = 0;
int b = 2;
int c = a + b
代码中a=0和b=2之间不存在依赖关系(Happens-Before关系),即他们的执行顺序不会影响最终的执行结果,所以b=2可能会被排序到a=0之前执行。但是a=0和b=2不会被重排序到c=a+b之后执行,因为他们之间存在依赖关系(Happens-Before关系)。
为什么要防止重排序
在单线程情况下,重排序可以给程序带来更高的执行效率。但是在有些情况下重排序就会带来不可预测的问题。如下面代码所示。
public class PossibleReordering{
static int num = 0;
static boolean flag = false;
public static void main(String[] args) throws InterruptedException{
Threadd one = new Thread(new Runnable(){
public void run(){
while(!flag){
}
System.out.println(num);
}
});
one.start();
num = 44; \\ 语句1
flag = true; \\ 语句2
}
}
在没有重排序的情况下会正常输出44,但如果语句1和语句2交换了顺序,则输出0。因此重排序在某些情况下会导致线程安全问题。
可见性问题
在多线程的情况下回导致可见性问题,如下图所示,线程A和线程B都拥有各自的工作内存,工作内存是线程私有的,即对其他线程不可见。主存中存在一个共享变量a=0,且线程A和线程B的工作内存中都保存了共享变量a=0的副本。如果共享变量并没有被volatile变量修饰,那么线程A或线程B对于工作内存中共享变量a的副本的修改可能不会马上刷新到主存中,也就是说线程A的工作内存中的副本a可能已经被修改成了5,但这些变化对于线程B并不可见。
如下图所示,如果共享变量a是volatile变量,则线程A对于副本的修改会立即刷新到主存中并通知其他线程,被volatile修饰的变量可以保证对于变量的写操作位于读操作之前。
volatile解决不了原子性问题
当线程A和线程B同时要对主存中的共享变量a进行加1操作时,会首先将共享变量a读取到自己的工作内存中,即此时线程A和线程B的工作内存中都存储了共享变量a=0的副本。然后线程A和线程B分别对各自工作内存中a变量的副本进行加1操作,然后写回主存中,主存中a变量的最终结果是1,但a实际上被进行了两次加一操作。