volatile原理

1,缓存一致性
来源:多核cpu,每个cpu拥有自己的缓存,当一个缓存修改时,另一个缓存数据过时了。
2,“snooping(窥探)”协议:常用的缓存一致性协议都是属于“snooping(窥探)”协议,各个核能够时刻监控自己和其他核的状态,从而统一管理协调。窥探的思想是:CPU的各个缓存是独立的,但是内存却是共享的,所有缓存的数据最终都通过总线写入同一个内存,因此CPU各个核都能“看见”总线,即各个缓存不仅在进行内存数据交换的时候访问总线,还可以时刻“窥探”总线,监控其他缓存在干什么。因此当一个缓存在往内存中写数据时,其他缓存也都能“窥探”到,从而按照一致性协议保证缓存间的同步。
3,MESI协议:MESI协议是一种常用的缓存一致性协议,它通过定义一个状态机来保证缓存的一致性。在MESI协议中有四种状态,这些状态都是针对缓存行(缓存由多个缓存行组成,缓存行的大小单位与机器的位数相关)。

(I)Invalid状态:缓存行无效状态。要么该缓存行数据已经过时,要么缓存行数据已经不在缓存中。对于无效状态,可直接认为缓存行未加载进缓存。
(S)Shared状态:缓存行共享状态。缓存行数据与内存中对应数据保持一致,多个缓存中的相应缓存行都是共享状态。该状态下的缓存行只允许读取,不允许写。
(E)Exclusive状态:缓存行独有状态。该缓存行中的数据与内存中对应数据保持一致,当某缓存行是独有状态,其他缓存对应的缓存行都必须为无效状态。
(M)Modified状态:缓存行已修改状态。缓存行中的数据为脏数据,与内存中的对应数据不一致。如果一个缓存行为已修改状态,那么其他缓存中对应缓存行都必须为无效状态。另外,如果该状态下的缓存行状态被修改为无效,那么脏段必须先回写入内存中。
比如:
Core0修改v后,发送一个信号,将Core1缓存的v标记为失效,并将修改值写回内存。
Core0可能会多次修改v,每次修改都只发送一个信号(发信号时会锁住缓存间的总线),Core1缓存的v保持着失效标记。
Core1使用v前,发现缓存中的v已经失效了,得知v已经被修改了,于是重新从其他缓存或内存中加载v。

MESI协议的定律:所有M状态下的缓存行(脏数据)回写后,任意缓存级别中的缓存行的数据都与内存保持一致。另外,如果某个缓存行处于E状态,那么在其他的缓存中就不会存在该缓存行。

MESI协议保证了缓存的强一致性,在原理上提供了完整的顺序一致性。可以说在MESI协议实现的内存模型下,缓存是绝对一致的,但是这也会导致一些效率的问题,我们平时使用的机器往往都不会采用这种强内存模型,而是在这个基础上去使用较为弱一些的内存模型:如允许CPU读写指令的重排序等。这些弱内存模型可以带来一定的效率提升,但是也引入了一些语义上的问题。
4,重排序:MESI协议保证了缓存的强一致性,但是其实在这个基础上还需要对CPU提出两点要求:

CPU缓存要及时响应总线事件
CPU严格按照程序顺序执行内存操作指令
只要保证了以上两点,缓存一致性就能得到绝对的保证。但是由于效率的原因,CPU不可能保证以上两点:

首先,总线事件到来之际,缓存可能正在执行其他的指令,例如向CPU传输数据,那么缓存就无法马上响应总线事件了
其次,CPU如果严格按照程序顺序执行内存操作指令,意味着修改数据之前,必须要等到所有其他缓存的失效确认(Invalidate Acknowledge),这个等待的过程严重影响CPU的计算效率,因此现代CPU大都采用存储缓冲(Store Buffer)来暂时缓存写入的数据,等所有的失效确认完成之后,再向内存中回写数据。正是因为使用了存储缓冲,导致一些数据的内存写入操作可能会晚于程序中的顺序,也就是重排序(reorder)。
另外,CPU的存储缓冲大小是有限制的,有一些数据的回写还是需要等待其他缓存的失效确认,而且失效操作本身也是比较耗时的,于是引入了失效队列(invalidation queue)的概念
对于到来的失效请求,失效确认消息必须马上发出;
发出消息后,失效操作放入失效队列,并不马上执行;
对于正在处理的缓存,CPU不给它发送任何消息
由于引入了存储缓冲和失效队列的概念,CPU的指令执行顺序就更加混乱,读操作有可能会读取到过时的数据(失效操作还在失效队列中),写操作完成的时间可能比程序中的时间要晚(写操作的数据在存储缓冲中)。对应于内存模型就分成了两个阵营:弱内存模型和强内存模型。弱内存模型的体系架构中,上述重排序优化的情况不能保证完全一致性,需要用户代码去保证,这样用户代码会比较复杂一些,CPU的执行效率也就更高。而在强内存模型的体系架构中则相反,CPU负责实现复杂的操作来保证一致性,用户代码简单但是执行效率低。
5,内存屏障:
read memory barrier (内存读屏障)
保障早于屏障之前的读操作之后再执行晚于屏障的读操作

write memory barrier(内存写屏障)
保障早于屏障之前的写操作之后再执行晚于屏障的写操作

full memory barrier(完全内存屏障)
保障早于屏障之前的读写操作之后再执行晚于屏障之后的读写操作

以上为处理器层面,对于jvm来说
1,内存屏障:

LoadLoad 读屏障例如有指令Load1和Load2那么假如插入屏障指令为Load1;LoadLoad;Load2,在中间插入LoadLoad屏障可以保障读操作不会进行乱序优化,即Load2在执行时,Load1的读操作应是执行完了的。
StoreStore 写屏障例如有指令Store1和Store2那么假如插入屏障指令为Store1;StoreSotre;Store2,在中间插入StoreStore屏障可以保障写操作不会进行乱序优化,即Store2在执行时,Store1的写操作应是执行完了的,并且Sotre1的写操作是对Store2可见的
LoadStore 读写屏障例如有指令Load1和Store2那么假如插入屏障指令为Load1;LoadStore;Store2,在中间插入LoadStore屏障可以保障前面的读操作和后面的写操作不会被乱序优化,即Store2执行时,Load1应是执行完了的
StoreLoad 写读屏障例如有指令Store1和Load2那么假如插入屏障的指令为Store1;StoreLoad;Load2,在中间插入StoreLoad屏障可以保障前面的写操作对后面的读操作不会被乱序优化,并且是可见的,即Load2执行时,Store1应是执行完了的,并其写操作对屏障后的读操作可见

2,volatile
windows下:

Java代码:instance = new Singleton();//instance是volatile变量
汇编代码:0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

简单理解:CPU提供了在执行指令期间提供了总线加锁的手段,那么加了lock的汇编生成机器码就使CPU在执行这条指令的时候会把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,从而保证这条指令执行的原子性
lock前缀的指令在多核处理器下会引发了两件事情。

将当前处理器缓存行的数据会写回到系统内存。
这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

这两件事情在IA-32软件开发者架构手册的第三册的多处理器管理章节(第八章)中有详细阐述。

Lock前缀指令会引起处理器缓存回写到内存 。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。在8.1.4章节有详细说明锁定操作对处理器缓存的影响,对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据 。

一个处理器的缓存回写到内存会导致其他处理器的缓存无效 。IA-32处理器和Intel 64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。它们使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。例如在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处理共享状态,那么正在嗅探的处理器将无效它的缓存行,在下次访问相同内存地址时,强制执行缓存行填充。
linux下:https://segmentfault.com/a/1190000016074254

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值