【摘要】 目录
可见性(可保证)
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(无效):一旦数据被标记为无效,那效果就等同于它从来没被加载到缓存中。
过程如下
- CPU C1从缓存中读取了缓存,其他CPU都没有读,这时这条缓存行的状态为Exclusive(独享)状态
- CPU C2 也从缓存中读取了缓存,这时这条缓存行的状态为Shared(共享)状态
- 当C1 CPU 修改了缓存,并回写到缓存中,这时这条缓存行的状态为Modified(修改)状态,然后会回写到主存中去
- 每个CPU读取完缓存行之后都在内存中监听已读缓存行的状态,这时C2 CPU 就会监听的缓存已被修改,此时,C2 CPU 就会把他设置为Invalid(无效)状态,无效状态的数据会被丢弃,如果想继续操作的话,还需要到主存中重新获取
- 最后,这条缓存在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协议(正确重排序的规则)
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作,volatile变量写,再是读,必须保证是先写,再读
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的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