volatile关键字很多人都听说过,但真正使用起来并不是那么简单容易,因为这关系到很多内存模型相关的知识点,本人最近在网上翻看了很多资料,在此做一下总结,根据前人的经验,我主要从四个方面进行解读:
* 内存模型的介绍
* 并发编程的三个要素
* Java内存模型
* volatile的原理和用法
最后会结合几个实例进行详细的说明
内存模型介绍
接触过操作系统和编译原理的人都知道,程序运行最终是通过指令来实现的,而指令是在cup中执行的,cup的计算速度相当的快。程序在执行过程中会涉及到数据的读取和写入,通常这些数据都是存储在主存(也就是通常说的内存)中,我们试想一下当cup在高速运行,这个时候需要读取或者写入计算结果,此时cup会一直等到去内存获取数据,这明显拖慢了cup的运行速度。
为此cup引入了高速缓存(cache)这个概念,即每个cup会有独立的一个高速缓存区,cup所需的数据会先从内存中拷贝一份备份数据到高速缓存,然后cup会使用高速缓存中的数据进行计算,计算结果也会存储到高速缓存中,最后cup会将高速缓存中的结果回写入内存中,完成数据的读取的和写入操作。
举例说明一下
i = i +1;
首先cup会将i这个变量的值从内存拷贝到高速缓存中,然后在缓存中对i进行加1操作,最后在将结果回写入内存中
我们来一起分析一下上面的操作过程,首先将i从内存拷贝到高速缓存中,在高速缓存中对i进行操作,然后才回写内存,那这中间就会导致内存中i的值和高速缓存中i的值会出现短暂的不一致,而这种不一致在单核cup中是没有任何影响的,因为单核cup我们可以理解成是一个顺序执行程序的通道,一次只能执行一个程序;但对于现如今多核cup来说就会出现结果不一致的并发问题
假设此时cup1和cup2都要对i进行运算操作
cup1首先从内存中获取i的拷贝到对用的高速缓存中,然后对i进行加操作
cuo2这个时候也从内存中获取到i的拷贝,同样对i进行加操作
然后cup1和cup2都将计算后的结果回写入内存中,结果i = 2,而实际上i的值应该等于3
以上问题就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。也就是说一个共享变量在多个CPU中被使用(常见于多线程问题中),就会出现不一致问题
为了解决缓存一致性问题,很早以前主要通过以下两种方式
在cup总线上加lock锁
通过缓存一致性协议
这两种方式都是在硬件层面上的处理
总线加锁
很早以前是通过在总线上加锁来实现一致性的,我们都知道cup是通过总线去访问内存区域的,当cup要访问内存的某个区域时,我们在总线上进行加锁操作,那么这个时候其它cup就无法访问该内存区域,只有等到总线锁释放后其它人才能访问到该区域中的数据,这就解决了缓存不一致的问题。
但总线加锁这种方式有个缺陷就是会阻塞cup,当两个cup同时访问一个内存区域时,只会有一个cup能拿到这个总线锁,势必会导致另一个cup阻塞等待,拖慢了cup的速度,因此后来就出现了缓存一致性协议。
缓存一致性协议
缓存一致性协议最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当cup在操作某个变量时,如果发现这个变量是一个共享变量,它就会去发消息信号通知其它cup的高速缓存置该变量的拷贝副本为无效状态,当其它cup要使用这个变量时发现变量状态是无效状态,就会去内存中重新获取数据的拷贝值,这样就可以实现缓存的一致性。