volatile关键字的可见性与原子性分析

volatile关键字其实在项目当中几乎没有用到,和synchronized不同的是,volatile不能保证原子性,但是却能够保证线程的可见性。volatile关键字其实属于比较难掌握的一部分,不建议新手去使用,宁愿使用代价大一点的synchronized去保证线程的同步安全,可见性,原子性,也不要使用难以掌握的volatile,当然,如果你的水平足够高,可以忽略,哈哈哈。volatile其实涉及到的知识点还是很多的,深入去分析一下,这里谈谈我学习volatile个人粗浅的总结。

要分析volatile的原子性,可见性,其实需要先谈谈java内存模型,因为这涉及到了这一部分的内容。java内存模型如图一所示,字有点丑,emmmm,但这不是重点。

图一 java内存模型

java内存模型有5个部分,分别是PC寄存器,本地方法栈,方法区,堆,java栈,在多线程去操作共享变量的时候,其实是线程去复制堆里面的该变量到自己的线程java栈里面,每一个线程直接操作的是java栈中的变量,即堆内存中该变量的副本;等修改完副本之后,再根据之前保存的变量信息,找到堆中该变量的位置,将值更新,或者如果变量副本失效了,则将堆中该变量的最新值load到自己的线程缓存中。

了解了这个模型操作之后,我们可以分析一下volatile可见性机制;volatile是如何操作的呢?这个涉及到了一个协议MESI协议:该协议解决了缓存的一致性问题,即如果CPU去写一个被volatile修饰的变量,那么它会在总线中发出一个信号,表明这个变量已经被更新,应该将别的副本缓存变为无效状态,当别的线程或者CPU去使用这个变量时,会先到嗅探总线去查看,是否有该变量更新的信号,如果有,即将自己本地缓存副本变为无效,重新到堆中load新的变量值到本地缓存java栈中,使用这样的机制来保证线程之间的共享变量可见性。

那为什么这样的机制不能保证变量的原子性呢?接下来的分析,需要慢慢来喔,举个例子,如果线程A,线程B去同时去load堆内存中的变量n最新值5,那么此时,线程A,线程B本地有自己的变量副本na ,nb都为5,线程A功能是将n加2,线程B功能是将n加3,如果是原子操作的话,那么计算之后答案应该是10。但是,如果这个过程中,线程A和线程B在修改本地na,nb过程时,同时到嗅探总线查探信号,发现没有改变信号,或者线程A先查探了嗅探总线,发现没有改变信号,这时候线程A阻塞了一会儿,之后线程B也去查探嗅探总线,发现也没有改变信号,此时,线程A,B本地的na,nb副本都是有效的,之后线程A执行完操作,将本地na改为7,这个时候到嗅探总线发出信号,表明该变量已经有更新,之后将堆内存n值更新为na值7,此时,由于线程B已经执行过探查操作,计算的nb的缓存值其实并不会改变,线程B最后执行完nb的值会为8,之后再刷回堆内存,此时n的值会为nb的值8。所以原子性并不能保证。

volatile的有序性即在程序执行到该代码片段时候,遇到volatile修饰的变量,会生成一个内存屏障barrier,处于该屏障内的代码段指令禁止重新排序。因为计算机的编译器是有优化功能的,程序语言java属于高级语言,在转化成机器语言的过程中,为了更加高效的计算,到汇编阶段,存在会对转换来的指令重新排序。

你可能感兴趣的:(volatile关键字的可见性与原子性分析)