在深入了解volatile关键字之前,我们有必要了解一些前提知识
Java并发编程的三个重要的特性
-
原子性
所谓原子性是指在一次操作或多次操作中,要么所有的操作全部都得到了执行并且不会收到任何因素的干扰而中断,要么所有的操作都不执行。
-
可见性
可见性是指,当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。
-
有序性
有序性是指程序代码在执行过程中的先后顺序,优于Java编译器以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码的顺序。
问题是什么?
内存结构
Java内存模型规定所有的变量都是存储在主内存中的,每个线程都有自己的工作内存,线程的工作内存保存了该线程使用的到的变量的主内存副本拷贝,线程对变量的所有操作都是在自己的工作内存中完成的,不同的线程之间对彼此工作内存中的变量都是不可见的,而线程间变量值的传递都是通过主内存来完成的,只有当线程执行完毕后才将自己工作内存中对变量的更新刷新到主内存中。这时候由于不同的线程对变量的不可见性,很容易出现“脏读”,因为线程私有堆栈中的数据和公共堆栈中的数据不同步造成的。
-
volatile关键字的作用
使用volatile关键字修饰变量时,会强制线程从公共堆栈中读取变量值,而不是从线程自己的私有堆栈中去读取,使其读取的变量值都是最新的,保证变量在线程间的可见性。
-
volatile关键字的内存语义
被volatile修饰的实例变量或者类变量存在两层语义:
一是保证了不同线程间变量操作的可见性,也就是说一个线程修改volatile修饰的变量,另一个变量会立即看到最新的值;
二是禁止对指令进行重排序操作
- volatile关键字保证可见性:
根据happens-before规则第三条:对于volatile变量的写要先于对这个变量的读
- volatile关键字保证顺序性:
对于volatile修饰的变量,jvm和处理器直接禁止对volatile关键字修饰的指令重排序,但是对于volatile前后无依赖关系的指令则可以随便怎么排序
-
volatile关键字的底层原理
被volatile修饰的变量存在于一个“lock;”指令的前缀,“lock;”前缀的作用相当于一个内存屏障,该内存屏障会为指令执行提供一下几个保证:
- 确保指令重排序时,不会将后面的代码排到内存屏障之前
- 确保指令重排序时,不会将前面的代码排到内存屏障之后
- 确保执行到内存屏障修饰的指令时,前面的代码全部执行完成
- 强制将线程工作内存中值刷新至主内存中
- 如果是写操作,强制其他线程工作内存中的缓存数据失效
参考:
《Java高并发编程详解》
《Java并发编程的艺术》