美文网首页JavaJava面试Java 杂谈
当面试官问volatile时,你应该知道些什么?

当面试官问volatile时,你应该知道些什么?

作者: 1d96ba4c1912 | 来源:发表于2017-07-15 00:02 被阅读211次

    Java面试中,面试者被问到volatile关键字的概率很高,怎么把它说清楚
    是我们需要思考的问题,本文就来盘点一下volatile的知识点。

    首先要明确volatile能干什么?不能干什么?

    volatile两大作用:
    1.保证内存可见性。
    2.禁止指令重排序。
    volatile使用误区:
    利用volatile修饰的整型变量做并发计数。
    注意:volatile无法保证原子性,计数时 i++ 这个操作不是原子的,因此上述做法是错误的

    了解了volatile的作用以后,我们再来看下这些问题产生的原因以及解决办法:
    1.内存可见性产生的原因:

    Java内存模型图
    上图为Java内存模型,由上图可知共享变量是保存在主内存中的,当一个线程需要操作共享变量时,需要从主内存拷贝一份共享变量的副本到本地内存(这个本地内存对其他线程是不可见的),此时如果线程修改了共享变量,则会把修改后的值写回到本地内存,但该变量由本地内存写回主内存的时间是不可控的,只要没有写回主内存后续线程就无法读取到该变量的最新值,这是其一。其二如果其他线程的本地内存中已经包含了该变量的副本,那么即使该线程把最新值写回到主内存,其他线程由于已经有副本了,所以重新去主内存读取该变量的时间也不可控。

    2.指令重排序产生的原因:
    程序运行时,在保证单线程执行逻辑不变的前提下,机器会根据当前上下文对指令的执行顺序进行调整来达到更高效的执行速度。
    重排序共有三种类型:编译器重排序,CPU重排序,内存读取重排序。
    重排序带来的问题网上有很多例子,这里列举一个容易被忽略的示例:
    当实例化一个类时,返回一个未完成初始化的对象就是指令重排序导致的,参考单例模式中的双检锁代码。

    接下来看看volatile是如何解决上面两个问题的:
    被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题。
    内存屏障的作用:
    1.在有内存屏障的地方,会禁止指令重排序,即屏障下面的代码不能跟屏障上面的代码交换执行顺序。
    2.在有内存屏障的地方,线程修改完共享变量以后会马上把该变量从本地内存写回到主内存,并且让其他线程本地内存中该变量副本失效(使用MESI协议)
    总结下内存屏障的类型:

    屏障类型 指令示例 说明
    LoadLoad Barriers Load1; LoadLoad; Load2 确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。
    StoreStore Barriers Store1; StoreStore; Store2 确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。
    LoadStore Barriers Load1; LoadStore; Store2 确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。
    StoreLoad Barriers Store1; StoreLoad; Load2 确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。

    内存屏障这里就不做过多介绍了,其实主要就是针对读写不同场景做出不同的调整,其次不同的处理器支持的屏障类型也不同。

    由上面的分析可以看出volatile在保证了可见性跟禁止重排序以外,并没有任何机制保证原子性。


    总结:面试中,关于volatile的问题了解这些内容应该就算是过关了,其实volatile还有很多很多细节的地方可以了解,但个人认为上面分析的这些内容虽然偏理论,但在工作或者学习中理解或者解决一些问题时会用到,其他细节的知识一般接触较少,如果真的感兴趣,希望把全部实现细节都研究清楚,那可以多去查查其他资料,就不单独在这里讨论了。

    相关文章

      网友评论

        本文标题:当面试官问volatile时,你应该知道些什么?

        本文链接:https://www.haomeiwen.com/subject/njzohxtx.html