volatile的字面解释是易变的,通常用于修饰变量,从字面中可以得知变量是易变的。理解比较简单,但是要用好确不简单。由于volatile关键字涉及java内存模型相关知识。所以了解volatile之前,我们先得学习一下内存模型相关知识,然后分析一下volatile关键字的原理,最后介绍几个使用场景。
目录大纲
- 内存模型的相关概念
- 并发编程中的三个概念
- java内存模型
- 深入剖析volatile关键字
- 使用volatile关键字场景
1.内存模型的相关概念
计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令的过程中也会涉及到数据的读取和写入。程序运行期间数据是存放在物理内存中的,CPU从内存中读取数据和写入数据相比CPU的执行速度要慢很多,因此如果对数据的操作要是CPU和内存直接进行交互的话,CPU的执行效率会大打折扣,所以在CPU中就引入了告诉缓存。
也就是说,当程序在运行的过程中,会将需要操作的数据直接从主存中复制到告诉缓存中,CPU在计算的时候直接从高速缓存中读写数据,操作完毕后由CPU告诉缓存将数据刷新到主存中。
2.并发编程中的三个概念
1.原子性:即一个操作或多个操作,要么全部执行程成功要么不执行。一荣俱荣、一损俱损差不就这意思吧。
最经典就是银行转账的例子了。
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。
试想一下,如果这2个操作不具备原子性, 会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。然后又从B取出了500元,取出500元之后,再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。
所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
2.可见性:当多个线程访问同一个变量时,一个线程修改了变量,其他线程能够立刻看的到修改的值。
例子
//线程1
int i=0;
i = 10;
//线程2
j = i;
假设执行线程1的时CPU1,执行线程2的是CPU2。当线程1执行时会将i的初始值从主存中读取到CPU1高速缓存中,然后CPU1操作过后i=10,当CPU1高速缓存还没将结果刷新到主存中时,线程2执行,它会从主存中读取数据到CPU2的高速缓存中,然后CPU2从高速缓存中读取数据进行操作写入CPU2高速缓存,最后主存中i是0是1都可能。
这就是可见性问题。
3.有序性:程序执行的顺序按照代码的先后顺序执行。
例子:
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
上面定义了int型变量,和boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上来说语句1是在语句2之前,但是JVM但是JVM在执行上时并不一定按照代码顺序进行执行,可能会进行指令的重排序。
指令重排序:是指处理器为了提高程序的运行效率,可能会对输入的代码顺序进行优化,但是保证最终程序的运行结果相同。
那么指令重排序是靠什么保证最终结果一致性呢?
例子:
int a = 10;//语句1
int r = 2;//语句2
a = a + 2;//语句3
r = a * a;//语句4
可能的执行顺序是:
不可能的顺序是:
因为处理器重排序会考虑语句之前的依赖关系,比如语句4是依赖语句3的。
虽然在单线程下处理器重排序不会出现啥状况那多线程情况下呢?
例子:
//线程1
context = loadContext(); //语句1
inited = true ; //语句2
//线程2
while(!inited){
sleep();
}
doSomethingWithConfig(context);
语句1 与语句2不存在语句依赖关系,此时线程1的处理器对语句进行了重排序,语句2先执行,此时线程2也开始执行直接跳过While语句执行doSomethingWithConfig(context);发现context还没有初始化,结果导致程序出现错误。
所以说指令重排序在单线程下不会影响程序的运行结果,在多线程的情况下却可能出现问题。
所以在并发编程中要保证程序的原子性,可见性,有序性。
3.java内存模型
前面谈到了内存模型以及并发编程中出现的一些问题。下面我们来看一下java内存模型,研究下java内存模型提供了哪些保证以及java中提供了哪些方法和机制让我们在多线程中保证程序执行的正确性。
在java虚拟机规范中试图定义一种java内存模型(Java Memory Model)来屏蔽各个硬件平台和操作系统的访问差异,保证在java程序在各种平台下都能达到一直的内存访问效果。在java内存模型中它定义了程序的执行顺序。为了获得较好的性能,java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序,所以在JMM中也是存在缓存一致性问题和指令重排序问题。
java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的私有内存,线程对变量的操作都必须在自己的私有内存中进行,不能对主存直接进行操作。