volatile修饰的共享变量,就具有了以下两点特性:
- 1.保证了不同线程对该变量操作的内存可见性;--->可见性
- 2.禁止指令重排序(内存屏障)----->有序性
什么是内存可见性?
在计算机执行程序的过程中,每条指令都是通过计算机cpu进行调度的,而指令执行过程中,势必涉及到读数据、写数据,而数据储存在主存当中,由于CPU执行的速度很快,而向主存读数据、写数据都很慢,如果每次都直接去主存读数据和写数据,势必会降低cpu的执行效率,因此cpu就有了高速缓存,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。但在多线程中就会出现缓存一致性问题(如果多核CPU,可能靠近不同的cpu cache中,在写数据时就会出现数据的不一致性),最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他线程中也存在该变量的副本,会发出信号通知其他线程将该变量的缓存行置为无效状态,因此当其他线程需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
什么是指令重排序?
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
有volatile修饰的变量,要满足数据依赖性(as-if-serial)【不管如何重排序(编译器与处理器为了提高并行度),(单线程)程序的结果不能被改变】,多了一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)
int a=1; //A指令
int b=2; //B指令
int c=a*b;//C指令
比如上面的A,B,C三条指令 ,为了加快运行速度,A,B指令可以同时进行,也同时可以进行指令重排,C指令依赖于A,B指令,所以C指令就不能重排序在A,B指令之前,要满足数据的依赖性(as -if-serival)和有序性。
volatile底层的实现机制?
如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。
0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp); |
lock前缀指令实际相当于一个内存屏障,内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障的功能:① 防止指令的重排序;②强制把写缓冲区/高速缓存中的脏数据等写回主内存,让其他缓存中相应的数据失效。