Java并发篇:volatile为什么不能保证原子性 ?

Java并发篇:volatile为什么不能保证原子性 ?,第1张

我个人总结理解的两方面影响:

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

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,但是还没来得及修改,就阻塞了,这时线程B开始了,他也读取到i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是0,之后对其进行加1 *** 作,得到i=1后,将新值写入到缓存中,再刷入主存(此时主存i由0变为0)中。

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

第二方面

因为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的原子性无法保证,如果加锁,可完全避免上述两点情况。

最后,如果文章有问题留言反馈,会及时更改,以免误导他人,

如果觉得有帮助,请帮忙点个赞!感谢!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/920284.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存