volatile详解
volatile的作用?
volatile关键字能够保证在多线程下共享变量可见性和程序的有序性.
原理:
首先,先介绍一下CPU多级缓存模型,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
为了降低CPU和内存速度上的差异,引入了多级缓存来暂存要处理的数据,当CPU要处理数据时,会首先去缓存中取,如果缓存中没有的话,才会去内存中查询。
当CPU对缓存中的数据进行修改以后,会将缓存中的数据刷新到内存中。
但是因为引入了缓存,也就导致了问题的发生(缓存一致性问题):
- CPU1读取数据a=1,CPU1的缓存中都有数据a的副本
- CPU2也执行读取操作,同样CPU2也有数据a=1的副本
- CPU1修改数据a=2,同时CPU1的缓存以及主内存a=2
- CPU2再次读取a,但是CPU2在缓存中命中数据,此时a=1
缓存一致性协议:
为了解决这个问题,早期是通过在总线上直接加锁的形式来解决缓存不一致的问题,但是直接加锁导致其他CPU不能访问,效率低下。后面出现了缓存一致性协议:
MESI协议:
有四种状态:
M: 当一个CPU的缓存行中的数据被修改了,该数据就会被修改为M状态
E: 当一个数据只被一个缓存所使用,该缓存行被置为E
S: 当多个CPU也读取了该数据到缓存中时,所有持有该数据的缓存行被置为S
I: 当一个CPU修改了数据被置为M,其他的缓存行会被置为I。
核心思想就是:当CPU对一个数据进行修改的时候,发现操作的变量是共享变量(发现其他CPU的缓存中也存有这个数据的副本),会通知其他CPU将该缓存行置为无效状态(I),当CPU去查询缓存时,发现某个缓存行的状态时无效状态时,会将这个缓存行丢弃,然后去内存中读取。
总线嗅探机制:
CPU对缓存行的监听和通知就是基于总线嗅探机制来实现的。
嗅探机制其实就是一个监听器,如果是加入MESI缓存一致性协议和总线嗅探机制之后:
- CPU1读取数据a=1,并同步缓存,并将缓存中的该缓存行设置为E
- 当CPU2读取数据a=2,总线嗅探机制发现CPU1中的缓存中也有a的副本,于是这些缓存行设置为S状态
- 当CPU1对数据进行修改时a=2,并将数据刷新到内存中,并且发现CPU1中缓存行的状态时S,此时总线嗅探机制会将其他CPU中缓存状态设置为I。
- 当CPU2去缓存中读取数据进行操作时,发现该数据的副本的状态为I,于是将该数据丢弃,然后去内存中重新读取并更新缓存。
当我们使用volatile关键字修饰某个变量之后,就相当于告诉CPU:我这个变量需要使用MESI和总线嗅探机制处理。从而也就保证了可见性。
指令重排序:
再加入MESI和总线嗅探机制后,当CPU2去内存中重新取值时,但是CPU1将数据刷新到内存中是需要时间的,如果读取的时候,CPU
总线嗅探机制后,当CPU2去内存中重新取值时,但是CPU1将数据刷新到内存中是需要时间的,如果读取的时候,CPU
1还没有将数据刷新完成,CPU2不会傻傻等着,会先处理后面的逻辑(前提是交换顺序不会影响程序的结果)。