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

   
我并不想具体介绍什么是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。
 

    

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信