volatile是怎么保证可见性和指令重排序的

volatile怎么保证可见性

每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,根据MESI思想,如果某个线程修改数据写回到主内存,其他线程能通过嗅探检查到本地数据无效,然后重新从主内存读取到自己的工作内存。

volatile怎么保证指令重排序

通过在指令间加入内存屏障来防止指令重排序。

基本概念

MESI

Modified(修改)、Exclusive(互斥)、Share(共享)、Invalid(无效)

CPU1使用共享数据时会先将数据拷贝到CPU1缓存中,然后置为独占状态(E),这时CPU2也使用了共享数据也会拷贝到CPU2缓存置为独占状态(E)。通过总线嗅探机制,当该CPU1监听总线中其他CPU对内存进行操作,此时共享变量在CPU1和CPU2两个缓存中的状态会被标记为共享状态(S)。

若CPU1将变量通过缓存回写到主存中,需要先锁住缓存行,此时状态切换为(M),向总线发消息告诉其他在嗅探的CPU该变量已经被CPU1改变并回写到主存中。接收到消息的其他CPU会将共享变量状态从(S)改成无效状态(I),缓存行失效。若其他CPU需要再次操作共享变量则需要重新从内存读取。

嗅探

每个处理器通过嗅探在总线上传播的数据来检查自己的缓存数据是不是过期了,当处理器发现自己缓存数据对应的内存地址被修改,就会将当前处理器的缓存数据设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

总线风暴

在java中使用unsafe实现CAS,而其底层由C++调用汇编指令实现的,如果是多核cpu是使用lock cmpxchg指令,单核cpu使用compxch指令。

如果在短时间内产生大量的CAS操作再加上volatile的嗅探机制则会不断地占用总线带宽,导致总线流量激增,就会产生总线风暴。

happens-before

从JDK1.5开始,提出了happens-before的概念,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。

 

注意点

1.volatile只能保证可见性,不能保证原子性。但用volatile修饰long和double可以保证其操作原子性。

2.volatile属性的读写操作都是无锁的。

 

猜你喜欢

转载自blog.csdn.net/Anenan/article/details/114955578