MESI缓存一致性协议--volatile能够保持可见性原因

volatile关键字,其实是轻量级锁,保证共享变量可见性的原因在于缓存一致性,协议主要是intel的MESI协议。

现在的cpu都是多核多级缓存架构的,多个cpu内核可以同时处理数据。

MESI缓存一致性协议--volatile能够保持可见性原因_第1张图片

主内存:我认为的是堆和方法区

工作内存:我认为是栈和cpu的三级缓存

JMM模型,我认为是一个多线程工作的规范,规范了多线程操作的数据在主内存和工作内存之间是怎么流转的,是jvm内存中的数据和cpu之间怎么协同工作,屏蔽掉了底层硬件的区别。

-----------------------------------------------------------------------------------------------------------------------------

一个变量在主内存和工作内存之间具体交互,JMM(java内存模型)定义了八大原子操作:

MESI缓存一致性协议--volatile能够保持可见性原因_第2张图片

 MESI缓存一致性协议--volatile能够保持可见性原因_第3张图片

如果多核同时操作同一个共享变量,就会存在变量数据不一致的问题,导致程序执行结果出错。

例如:

1、cpu0读取数据x=1副本到工作内存,cpu1此时也进行读取x=1副本到工作内存;

2、cpu0修改x=2,然后同步到主内存,此时在主内存和cpu0的x=2;

3、cpu1再次读取x,cpu1工作内存已经有一份x=1的副本

4、如果cpu1拿着旧数据再去修改x的值,那将会导致脏数据

因此,cpu1不能及时感知到cpu0已经将x修改成2了,造成了不可见问题。

针对这个问题,在早期cpu处理这个问题,使用的是总线锁(在总线上直接加锁)来解决缓存不一致问题,总线在被锁住的时候,其他cpu是无法访问主内存的;这种方法就如java1.6版本之前的Synchronized直接就是加重锁,简单粗暴,但是效率很低下。

现在的cpu都是采用缓存一致性协议来处理,主要缓存一致性协议用MESI协议。

MESI协议

主要是四种状态M(modified)修改,E(Exclusive)独占、互斥,S(Shared)共享,I(Invalid)无效

MESI缓存一致性协议--volatile能够保持可见性原因_第4张图片

当共享变量被volatile关键字修饰的时候,编译器编译成字节码后,解释执行器JIT优化成汇编指令,从解码的汇编指令看被volatile关键字修饰变量会有一个lock前缀修饰(idea怎么查看汇编指令自行百度),正是看到有lock前缀修饰的变量MESI协议才会生效。除了MESI协议之外,还需要总线嗅探机制来监听和通知,当cpu上来read读取变量的时候,发现变量有volatile修饰,那么cpu会监听总线,当有其他cpu读取和修改该变量的时候,总线会发通知给cpu。

MESI协议失效的情况是:

1、cpu的寄存器在处理变量的时候是不支持协议的,只有在工作内存(cpu三级缓存)和主内存中有效。

2、MESI缓存一致性协议是针对单个缓存行cacheLine(一个缓存行的大小是64byte)进行加锁,如果变量超过缓存行的大小,那么缓存一致性协议会失效,会升级为总线锁来处理。

MESI+总线嗅探机制过程:

1、cpu0从主内存读取x=1副本到cpu缓存中,cpu0监听总线,此时只有cpu0有x,所以此时是独占状态E

2、cpu1也从主内存读取x=1副本到缓存中,根据总线嗅探机制,总线通知cpu0,告知cpu1也读取了变量x,此时cpu0的x变量状态变成S共享状态,总线也会通知cpu1让它变成S共享状态。

3、cpu0想把x修改成2,把x加载到cpu寄存器中修改成x=2,然后写入到一个叫StoreBuffer缓存中,并没有立马修改cpu缓存中x的值,

4、此时,cpu0想修改x的状态变为M修改状态,cpu0发起消息通知总线,总线收到通知发现只有cpu0在修改x,那会同意cpu0设置为修改状态,cpu0收到总线通知,cpu0把storeBuffer中的x=2的值写入cpu缓存中,把状态修改为M,

5、总线通知cpu1,通知他cpu0已经修改x的值并写入到主内存,让cpu1修改x状态为I失效状态,并去主内存拉取最新的x的值;cpu1收到通知后,把缓存中的x=1写入到一个queue队列中,然后把缓存中的x设置为I无效状态,至于cpu1什么时候删除队列中的数据,看cpu1什么时候有空,

6、cpu0把修改后的x=2写回主内存,然后cpu0的x修改成E独占状态,总线嗅探机制通知cpu1去主内存获取最新的x的值,cpu1收到通知后去主内存拉取最新的x值到缓存中,此时cpu0和cpu1都有x副本,x都会变成S共享状态。

MESI缓存一致性协议--volatile能够保持可见性原因_第5张图片

7、并发的时候,如果cpu0和cpu1同时修改x的值,都想让总线把自己的x改成M修改状态,此时总线会做出相应的裁决,将其中一个设置为M状态,一个设置为I状态。

MESI协议+总线嗅探机制,让多线程操作volatile共享变量的时候,保证可见性。但是不能保证原子性,比如:x++操作,这个操作其实分为三个步骤,读取x变量的值,进行x+1操作,最后值赋值给x。这是三个步骤,多线程中,cpu0在执行完x++后改成M状态,cpu1也执行了x++,此时cpu1需要丢弃x的值。cpu0写入主内存,cpu1修改的值作废了。正常两个线程操作需要加2次,现在cpu1的作废了,只加了1次,cpu1被中断了。保证不了原子性。

volatile能够防止指令重排,编译器编译和cpu执行的时候,会对指令进行重排来提高执行效率。

因为volatile修饰的变量,在编译成字节码的时候可以看到会在volatile代码前后加入内存读写屏障。JVM有四种内存屏障,有storeStore  storeLoad  loadLoad  loadStore.

你可能感兴趣的:(面试题,并发编程)