volatile

【摘要】 目录
可见性(可保证)
MESI协议
过程如下
有序性(可保证)
重排序
volatile保证有序性的原理靠的是内存屏障
内存屏障的类型
volatile都加了哪些内存屏障
happens-before协议(正确重排序的规则)
原子性(不可保证)

可见性(可保证)

如图,可见性问题的产生源于内存模型,共享内存被多个线程同时使用的时候会产生如下的…

目录

可见性(可保证)

MESI协议

过程如下

有序性(可保证)

重排序

volatile保证有序性的原理靠的是内存屏障

内存屏障的类型

volatile都加了哪些内存屏障

happens-before协议(正确重排序的规则)

原子性(不可保证)



可见性(可保证)

  • 如图,可见性问题的产生源于内存模型,共享内存被多个线程同时使用的时候会产生如下的问题(可见性问题)

1.不知道被变更的数据何时从工作内存store write 到主内存

2,即使刷回主内存,但并不知道另外的线程何时感知到, 然后重新加载新的数据

  • 能够保证可见性的原理是,MESI一致性协议和 cpu嗅探机制

1,MESI是一整套的保证机制,volatile能做到当共享变量assign操作完成以后,立马进行store和write

2,同时其他cpu内的共享变量立马被过期掉, 如果再使用则重新在主内存中加载(通过cpu嗅探机制)

3,这些机制的指令就是:lock前缀指令

  • 执行指令是:lock 前缀指令

MESI协议

  • Modified(修改):数据被修改了,和内存中数据不一致,数据只存在于本Cache中。
  • Exclusive(独享):数据和内存中的数据一致,数据只存在于本Cache中。
  • Shared(共享):数据和内存中的数据一致,数据存在多个Cache中。
  • Invalid(无效):一旦数据被标记为无效,那效果就等同于它从来没被加载到缓存中。

过程如下

  1. CPU C1从缓存中读取了缓存,其他CPU都没有读,这时这条缓存行的状态为Exclusive(独享)状态
  2. CPU C2 也从缓存中读取了缓存,这时这条缓存行的状态为Shared(共享)状态
  3. 当C1 CPU 修改了缓存,并回写到缓存中,这时这条缓存行的状态为Modified(修改)状态,然后会回写到主存中去
  4. 每个CPU读取完缓存行之后都在内存中监听已读缓存行的状态,这时C2 CPU 就会监听的缓存已被修改,此时,C2 CPU 就会把他设置为Invalid(无效)状态,无效状态的数据会被丢弃,如果想继续操作的话,还需要到主存中重新获取
  5. 最后,这条缓存在C1 CPU中的状态又会改为Exclusive(独享)状态

有序性(可保证)

重排序

1,在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序

2,为了保证顺序,JMM会禁止部分重排序

volatile保证有序性的原理靠的是内存屏障

内存屏障的类型

1,LoadLoad屏障:Load1;LoadLoad;Load2,确保Load1数据的装载先于Load2后所有装载指令,他的意思,Load1对应的代码和Load2对应的代码,是不能指令重排的

2,StoreStore屏障:Store1;StoreStore;Store2,确保Store1的数据一定刷回主存,对其他cpu可见,先于Store2以及后续指令

3,LoadStore屏障:Load1;LoadStore;Store2,确保Load1指令的数据装载,先于Store2以及后续指令

4,StoreLoad屏障:Store1;StoreLoad;Load2,确保Store1指令的数据一定刷回主存,对其他cpu可见,先于Load2以及后续指令的数据装载

volatile都加了哪些内存屏障

每个volatile写操作前面,加StoreStore屏障,禁止上面的普通写和他重排

每个volatile写操作后面,加StoreLoad屏障,禁止跟下面的volatile读/写重排

每个volatile读操作后面,加LoadLoad屏障,禁止下面的普通读和voaltile读重排

每个volatile读操作后面,加LoadStore屏障,禁止下面的普通写和volatile读重排

happens-before协议(正确重排序的规则)

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作,volatile变量写,再是读,必须保证是先写,再读
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

原子性(不可保证)

  • 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性
  • 但是 32位操作系统 Long Double 类型操作并非原子性,  而volatile能保证 Long Double类型操作的原子性
  • volatile为什么不能保证原子性(i++场景就保证不了原子性)

如上内存模型的图, 当flag的值被两个线程同时读到工作内存,

同时进行变更,

同时写回到工作内存,

这个时候不管谁先写会主存, 即使再通过MESI协议让缓存失效也已经来不及了, 这时候可能造成所有的变更都会被写会主存

文章来源: blog.csdn.net,作者:享受技术厚积薄发,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/bemavery/article/details/116241025

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享