为什么无法保证原子性和保证单条指令的可见性和原子性

【java】volital关键字为什么无法保证原子性和保证单条指令的可见性和原子性
我个人总结理解的三方面影响:

简单举例说明,就不用看下面详细解释了:

i=0; A,B2个线程各i++ 50000次
1.线程A 读到i=0时,A阻塞,B也读到i=0,进行i++ 写到主存,此时i=1,A继续执行进行i++,i=1写回主存。
2.MESI协议中,AB线程同时进行修改i,此时裁决机制进行裁决设缓存A为M状态,缓存B为I状态,那么B线程修改无效,并且浪费一次,A进行i++把i=1写回主存。



分别执行三次,每次结果各不相同:

输出最后结果:93873

输出最后结果:80319

输出最后结果:95797

第一方面(无法保证多条指令原子性)

假设线程A首先读取到了i的0,进行+1操作,但是还没来得及更新,就阻塞了,这时线程B开始了,他也读取到i的值,由于i的值未被更新,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是0,之后对其进行加1操作,得到i=1后,将新值写入到缓存中,再刷入主存(此时主存i由0变为1)中。

此时如果A线程被唤醒,A线程将继续执行更新操作,写入到缓存中,再输入到主存中(此时主存i由1变为1),多次线程竞争阻塞导致多次浪费,所以达不到预期100000

总结来说:

i++操作是非原子操作,i++的过程分三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。你说的emsi确实导致了缓存行失效,但是这三步如果已经进行了两步,第三步的时候如果其他线程更改了值,你察觉到了也没用,你已经读过赋过值了。

第二方面(可保证单条指令原子性和可见性)

因为volatile底层使用的是缓存一致性协议,会根据MESI原则。

MESI分别代表缓存行数据所处的四种状态,通过对这四种状态的切换,来达到对缓存数据进行管理的目的。

M 修改 Modified : 该缓存行有效,且该缓存行数据被修改了,和内存中的数据不一致,数据只存在于本缓存行中,此时状态为M ;

E 独占 Exclusive : 该缓存行有效,且该缓存行数据和内存中的数据一致,此时状态为E;

S 共享 Shared : 该缓存行有效,且该缓存行数据和内存中的数据一致,数据同时存在于其他缓存中(同步到主内存中完成后 , 标记为共享状态 ; 共享状态的变量才能被线程加载到工作内存中),此时状态为S ;

I 失效 Invalid : 该缓存行无效,线程 A , B 中都使用了同一个共享变量 , 如果在线程 A 中修改该变量 , 线程 B 中的变量更新前都标记为失效状态,此时A状态为M状态,B状态为I状态 ;

1、CPU1从内存中将变量i加载到缓存中,并将变量i的状态改为E(独享),并通过总线嗅探机制对内存中变量i的操作进行嗅探(因为有volatile关键字修饰,如果没有修饰则不会进行嗅探,说明可见性)


2、此时,CPU2读取变量i,总线嗅探机制会将CPU1中的变量i的状态置为S(共享),并将变量i加载到CPU2的缓存中,状态为S



3、CPU1对变量i进行修改操作,此时CPU1中的变量i会被置为M(修改)状态,而CPU2中的变量i会被通知,改为I(无效)状态,此时CPU2中的变量i做的任何修改都不会被写回内存中(高并发情况下可能出现两个CPU同时修改变量i,并同时向总线发出将各自的缓存行更改为M状态的情况,此时总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效)


4、CPU1将修改后的数据写回内存,并将变量i置为E(独占)状态



5、此时,CPU2通过总线嗅探机制得知变量i已被修改,会重新去内存中加载变量i,同时CPU1和CPU2中的变量i都改为S状态!



这就是完整的MESI流程,因为第3步中,高并发竞争总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效,浪费一次i++,所以i也达不到100000。

第三方面(单条指令的内存模型保证单条数据的原子性)

Ø·在每个volatile写操作的前面插入一个StoreStore屏障。

Ø·在每个volatile写操作的后面插入一个StoreLoad屏障。

Ø·在每个volatile读操作的****后面插入一个LoadLoad屏障。

Ø·在每个volatile读操作的****后面插入一个LoadStore屏障。

#Java##程序员#
全部评论

相关推荐

点赞 评论 收藏
转发
1 收藏 评论
分享
牛客网
牛客企业服务