1,volatile 是如何保证 happens-before 规则(内存可见性)的问题
使用 缓存锁
物理层面分析
高速缓存从下到上越接近 CPU 速度越快,同时容量也越小。现在大部分的处理 器都有二级或者三级缓存,从下到上依次为 L3 cache, L2 cache, L1 cache. 缓 存又可以分为指令缓存和数据缓存,指令缓存用来缓存程序的代码,数据缓存 用来缓存程序的数据
L1 Cache,一级缓存,本地 core 的缓存,分成 32K 的数据缓存 L1d 和 32k 指 令缓存 L1i,访问 L1 需要 3cycles,耗时大约 1ns;
L2 Cache,二级缓存,本地 core 的缓存,被设计为 L1 缓存与共享的 L3 缓存 之间的缓冲,大小为 256K,访问 L2 需要 12cycles,耗时大约 3ns;
L3 Cache,三级缓存,在同插槽的所有 core 共享 L3 缓存,分为多个 2M 的 段,访问 L3 需要 38cycles,耗时大约 12ns;
缓存一致性问题(内存可见性问题)
CPU-0 读取主存的数据,缓存到 CPU-0 的高速缓存中,CPU-1 也做了同样的事 情,而 CPU-1 把 count 的值修改成了 2,并且同步到 CPU-1 的高速缓存,但 是这个修改以后的值并没有写入到主存中,CPU-0 访问该字节,由于缓存没有 更新,所以仍然是之前的值,就会导致数据不一致的问题 引发这个问题的原因是因为多核心 CPU 情况下存在指令并行执行,而各个 CPU 核心之间的数据不共享从而导致缓存一致性问题,为了解决这个问题, CPU 生产厂商提供了相应的解决方案
总线锁
当一个 CPU 对其缓存中的数据进行操作的时候,往总线中发送一个 Lock 信 号。其他处理器的请求将会被阻塞,那么该处理器可以独占共享内存。总线锁 相当于把 CPU 和内存之间的通信锁住了,所以这种方式会导致 CPU 的性能下 降,所以 P6 系列以后的处理器,出现了另外一种方式,就是缓存锁。
缓存锁 : 如果内存区域的数据被两个处理器缓存,则该内存数据被阻止同时修改
Java代码如下。 i
instance = new Singleton(); // instance是volatile变量
转变成汇编代码,如下。
0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);
如果缓存在处理器 缓存行中的内存区域在 LOCK 操作期间被锁定,当它执行锁操作回写内存时,处理不在总线上声明 LOCK 信号,而是修改内部的缓存地址,然后通过缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域的数据,当其他处理器回写已经被锁定的缓存行的数据时,会导致该缓存行无效。 所以如果声明了 CPU 的锁机制,会生成一个 LOCK 指令,会产生两个作用
1. Lock 前缀指令会引起处理器缓存回写到内存,在 P6 以后的处理器中, LOCK 信号一般不锁总线,而是锁缓存
2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
缓存一致性协议
处理器上有一套完整的协议,来保证 Cache 的一致性,比较经典的应该就是 MESI 协议了,
它的方法是在 CPU 缓存中保存一个标记位,这个标记为有四种 状态
Ø M(Modified) 修改缓存,当前 CPU 缓存已经被修改,表示已经和内存中的 数据不一致了
Ø E(Exclusive) 独占缓存,当前 cpu 的缓存和内存中数据保持一直,而且其他 处理器没有缓存该数据
Ø S(Shared) 共享缓存,cpu 的数据和内存中数据一致,并且该数据存在多个 cpu 缓存中 ,
每个 Core 的 Cache 控制器不仅知道自己的读写操作,也监听其它 Cache 的读 写操作,嗅探(snooping)"协议
Ø I(Invalid) 失效缓存,说明 CPU 的缓存已经不能使用了
CPU 的读取会遵循几个原则
1. 如果缓存的状态是 I,那么就从内存中读取,否则直接从缓存读取
2. 如果缓存处于 M 或者 E 的 CPU 嗅探到其他 CPU 有读的操作,就把自己的缓 存写入到内存,并把自己的状态设置为 S
3. 只有缓存状态是 M 或 E 的时候,CPU 才可以修改缓存中的数据,修改后,缓 存状态变为 MC
2,volatile 是如何保证防止内存重排序的
通过内存屏障指令
硬件: Load Barrier Store Barrier
volatile 写操作之间插入 storestore屏障 , 在写操作之后插入storeload屏障
volatile 读操作之前插入loadload屏障、 在读操作之后插入loadstore屏障
JAVA指令:
load load load1 ; load load; load2
load store load1 loadstore store2
store store store1 storestore store2
store load store storeload load