前言

自学了一年JAVA阿巴阿巴终于约到了面试,这次面试官让她谈谈对JMM和volatile的理解。

面试官: 了解JMM吗,讲一下JMM。

阿巴阿巴: 知道一些,JMM是JAVA内存模型(JAVA Memory Model),目的是为了屏蔽各种硬件和操作系统之间的内存访问差异,从而让JAVA程序在各种平台对内存的访问一致。

阿巴阿巴: 不仅如此,JMM还规定了所有的变量都存储在主存中,每个线程都有自己独立的工作空间,线程对变量的操作必须先从主存中读取到自己的工作内存中然后再进行操作,最后回写回主存。

阿巴阿巴: 关于主存和工作内存的交互JAVA定义了八种操作来完成,且这些操作都是原子性的:lock、unlock、read、load、use、assign、store、write

面试官: 不错不错,那JMM是真实存在的嘛,和JVM内存模型(JAVA 虚拟机内存模型)是一样的嘛?

阿巴阿巴: 不是真实存在的,JMM讲的也只是一种模型,真实的实现可能还是和模型会有差异的。JMM和JVM是不一样的,它们并不是同一个层次的划分,基本上没啥关系。

堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程私有的
程序计数器是这几块区域唯一一个不会发生OOM的区域

面试官: 理解的还不错嘛,那你讲讲Volatile关键字呗

阿巴阿巴: Volatile可以说是JAVA虚拟机提供的最轻量级的同步机制,当一个变量被定义为volatile后,它将具备俩种特性,第一个是保证此变量对所有线程的可见性,即当一个线程改变了这个变量的值后,其他线程能够立即感知的到,虽然具有可见性,但是多线程在并发情况下对volatile修饰的变量进行操作时是会有线程安全性的问题的。这是因为volatile修饰的变量在各个线程工作内存中是不存在一致性的,但是由于每次使用都要进行刷新,导致执行引擎看不到不一致的情况。

阿巴阿巴: Volatile修饰的变量的第二个特性是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有依赖的赋值结果的地方都能够获取到正确的结果。而不能保证赋值的顺序和代码中的书写顺序一致。例如下面的DCL的单例模式。

public class Instance {
    private String str = "";

    private volatile static Instance ins = null;
    /**
     * 构造方法私有化
     */
    private Instance(){
        str = "hi";
    }

    /**
     * DCL获取单例
     * @return      */
    public static Instance getInstance(){
        if (ins == null){
            synchronized (Instance.class){
                if (ins == null){
                    ins = new Instance();
                }
            }
        }
        return ins;
    }
}

阿巴阿巴: 如果上面ins变量不使用volatile变量进行修饰,那么当线程A在获取了Instance.class锁后,对ins变量进行 ins = new Instance() 初始化时,由于这是很多条指令,jvm可能会乱序执行。这个时候如果线程B在执行if (ins == null)时,正常情况下,如果为true,说明需要获取Instance.class锁,等待初始化。但是这时候,假设线程A再没有对ins进行初始化完,比如只分配了空间,对象还没构造完,但是已经将引用返回了,这样线程B得到的就是一个未能实例化完全的对象,从而发生异常。而加了volatile关键字后,如果实例还未初始化完成,那么它的引用是不会向外发布的,这样即可避免异常的发生。

面试官: 不错,你这块都掌握的挺扎实的,明天可以来上班了。

阿巴阿巴: 好的

分享不易,如果你觉得文章还不错,你的转发、分享、点赞、关注、留言就是对我最大的鼓励。感谢您的阅读!