回顾
指令重排
第一V读,都不能指令重排;第二个V写,都不能指令重排
普通读写,写读都会发生指令重排,V写+普通读写会发生指令重排,普通读写+V读会发生指令重排
CPU缓存一致性协议MESI
java—》cpu的执行过程
.class文件–经过类装载子系统----装载到元空间----根据.class文件会在堆里生成Class结构化对象----创建线程栈----线程创建后机会执行.class中定义的方法的字节码—操作字节码的过程中需要程序计数器和解释执行器/jit优化转为汇编指令【字节码翻译成汇编指令,时间比较长】—再翻译成二进制供CPU读取【时间比较短】
CPU怎么知道什么时候调用这个线程呢?
答:
CPU用的是内核线程模型,在操作系统底层维护OS线程变量池【维护有线程表,当CPU调到该线程时,二进制就会方法CPU上执行 】,该线程池与JVM虚拟机栈是一对一的
volatile的加入,字节码显示会有lock前置锁----》触发硬件缓存锁定机制【总线锁、缓存一致性协议】
总线锁:早期使用总线保证缓存一致
早起CPU要是多核的话,导致两个CPU共同操作一个变量X【两个CPU都是读的话没问题】,要是两个CPU都对变量进行写的话,就会导致不知道执行哪个CPU的问题,所以前期就采用lock前缀加总线锁【总线的作用:CPU跟内存之间访问是通过总线进行的】
所以前期在总线上加lock前缀锁的时候就会影响CPU跟内存进一步的信息交流进而影响CPU的执行【CPU就类似于单核,CPU的多核效果发挥不出来】,导致性能很低
缓存一致性协议:
加入总线嗅探机制
CPU启动后采用监听模式一直监听总线
volatile可见性原理
加volatile后会在汇编层面加lock前缀,lock前缀就会触发缓存一致性协议,即时有多个线程同时进行操作,也能保持可见性
线程A与线程B从主存读取数据放到各自的工作内存,各标记S状态【共享,只有一个读取得时候是标记E状态(独占)】,当一个线程改了【改之前会加锁(锁缓存换行,缓存行为64字节大小,原子安全的;要是缓存行装不下,缓存一致性协议就会升级为总线锁),由总线去裁决哪个线程加锁】,改完后就会更改状态为M ,并写回主存,因为嗅探机制另一个线程就会嗅探到数据已更改就会改状态为I从而舍弃从新重主存读取数据
/*第一次把MESI理解的这么透彻! 举个栗子,使用volatile关键字修饰的属性,当两个或多个线程通过总线去主内存获取这个属性的值时,会对总线加一个lock前缀,早期总线加了lock前缀的话,其他线程都会阻塞,不能获取属性值,导致性能非常低。进而就设计出了缓存一致性协议,即MESI。MESI就是当线程1通过加了lock前缀的总线读取主内存数据到线程的工作内存后,会给这个属性加一个状态E,表示独占状态,这时如果另一个线程也通过总线到主内存读取该属性,复制到该线程的工作内存中,线程1感知到后属性状态就会从E边为S,即从独占状态变成共享状态,另一个线程中该属性的状态也是共享状态。如果两个线程都要修改这个属性的值,会对各自属性所在缓存行加一个锁,向总线发出加锁的消息,总线裁决后胜出的线程修改成功,更改属性状态为M状态,即修改状态,并且通知到其他线程,其他线程就会从S状态改变为I状态,即从共享状态改为无效状态,并且刷新缓存值。这就是MESI缓存一致性协议。 另外为什么说volatile能保证可见性和有序性,但无法保证原子性?因为volatile是对线程的工作内存有效,对寄存器是无效的,有可能寄存器已经读取到当前的属性值,之后属性值才被置为无效,这是刷新缓存值对寄存器也没有作用了,所以volatile保证不了原子性。*/