卧槽,为什么 volatile 关键字保证不了线程安全啊

点击上方“java大数据修炼之道”,选择“设为星标”

优质文章和精品资源, 第一时间送达

作者:Aaron_涛

blog.csdn.net/qq_33330687/article/details/80990729

在当前高并发的时代,不懂一点高并发多线程都不好意思出去,即使没地方使用,网上大多数相关文档博客也都讲解了这些部分。

我并不想具体介绍什么是volatile,我写这篇博客目的是说明白为什么volatile保证不了线程安全。想要线程安全必须保证原子性,可见性,有序性。而volatile只能保证可见性和有序性

在说明这个问题之前,首先还是要说明下cpu和内存,cpu和内存直接是有高速缓存的,一般分为多级。cpu首先是要从内存中读取一个数据进缓存,然后从缓存中读取进行操作,将结果返回给缓存,再把缓存写回内存。

如果同一个变量i=0,有两个线程执行i++方法,线程1把i从内存中读取进缓存,而现在线程2也把i读取进缓存,两个线程执行完i++后,线程1写回内存,i = 1,线程2也写回内存i = 1,两次++结果最终值为1,这就是著名的缓存一致性问题。为了解决这个问题,前人给了两种方案

  • 总线锁

  • 缓存一致性协议

cpu为了和各个硬件打交道方便,设计师们把每个硬件都连接一个线到cpu,但是发现这样太麻烦了,所以改为所有硬件都挂在总线上,cpu通过总线和各个硬件打交道。

如果使用总线锁,就阻塞了其他cpu和其他硬件交互(内存之类,磁盘,等等),i++这条语句就必须执行完了,其他cpu才能执行,否则只能一个cpu去和硬件交互。这也是一种解决办法,问题也明显,特别效率低下。

为了解决这个问题提出缓存一致性协议,具体协议就不讲,简单解释一下,如果我写入之后发现这是共享变量就使得其他cpu缓存了的值失效,让它再次去内存中读取。

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

解释一下这段话的内容,重排序的指代码的真正执行过程可能不是代码书写的顺序,这是为了是cpu流水线作业提高cpu的利用率而优化的一门技术。

而lock前缀指令(内存屏障),一个屏障会把这个屏障前写入的数据刷新到内存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。

这样咋一看貌似可以保证线程的安全性呀,为啥不能保证呢

这样如果有一个变量i = 0用volatile修饰,两个线程对其进行i++操作,如果线程1从内存中读取i=0进了缓存,然后把数据读入寄存器,之后时间片用完了,然后线程2也从内存中读取i进缓存,因为线程1还未执行写操作,内存屏障是插入在写操作之后的指令,意味着还未触发这个指令,所以缓存行是不会失效的。然后线程2执行完毕,内存中i=1,然后线程1又开始执行,然后将数据写回缓存再写回内存,结果还是1。

-END-

如果看到这里,说明你喜欢这篇文章,请 转发、点赞。同时 标星(置顶)本公众号以便可以第一时间接受到博文推送。

1. IDEA新特性:提前知道代码怎么走!

2. 盘点 10 个代码重构的小技巧

3. Java开发必须掌握的 20+ 种 Spring 常用注解

4太赞了! 墙裂推荐这33个IDEA最牛配置, 好用到爆 ! 拿走不谢 !

看完有收获?请转发分享给更多有需要的人

关注 java大数据修炼之道

每天学习java技术,你想学的Java知识这里都有!

微信扫描二维码,关注我的公众号

写留言

  • 如果对你有帮助,请点个"在看 ",谢谢 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值