聊聊JVM —— 理解java内存模型

前言:

        这几天,请教了我的大神师兄,他和我说了在大厂面试的经过,四面面到他怀疑人生,最终他拿到了 sp 级别的 offer,很感谢师兄他把面试中各种知识点向我分享。接下来,笔者会针对该次面试所要求的知识点进行学习强化,并写成博客,与大家分享。师兄所在学校是三本(名气也一般般),和他同期面试的最低都是 985 211 好像还有硕士级别的。个人认为,有实力才是真的牛逼,学历只是敲门砖,没有必要因为自己的学历低而没自信。要学习师兄那种,同台竞争,展示自己的实力即可,粗暴一些说:教他们怎么做人(开玩笑哈,只是告诉大家选好方向,努力就好)。

        接下来是二面的部分知识点: 也是面试里面最简单的部分了 —— java 内存模型。

 

java 内存模型:

       java 内存模型的主要目标是定义程序中的各个变量的访问规则,包括静态变量,数组对象,以及实例变量。不包括为局部变量(方法参数也属于局部变量),因为局部变量是线程私有,不存在竞争关系,所以不用理会。

       如下图,我们代码所有的变量都是存在主内存中的。此外每条线程都有自己的工作内存,工作内存保存了线程所需要使用到的主内存变量的副本拷贝,线程对变量的所有操作都必须在工作内存中完成,而不是直接去操作主内存。在一条线程执行时,该线程是无法直接访问其他线程的工作内存中的变量,只有当该线程将方法内指令(代码)全部执行完后,回写到主内存中时,其他线程开可见。

               

     小结:总体来看,线程与线程间的通信必须经过主内存才可以进行交互。这里知道局部变量是线程私有等就可以了。下面会介绍内存模型的三个特性,请往下看。

java 内存模型的三个特性:

  •   可见性:

        可见性是指当一个线程修改了共享变量的值时,其他线程能够立刻得到这个修改。         

  •   原子性:

        由 java 内存模型来直接保证的原子性变量操作。

        简单些可以用事务的方式来理解:一个操作不能在执行过程中不能被打断,要么全部完成,要么全部不完成,失败时回滚到操作之前的状态。

  •   有序性:

        在程序执行过程中,普通变量的赋值操作可能和我们写的代码顺序可能是不一致的,比如:代码变量的赋值操作在第四行,在执行过程可能第一个执行的代码就是赋值。即顺序可能不一致,但是虚拟机会保证变量在该方法执行过程中都能获得正确的结果。

 

java 中的关键字

volatitle 关键字

        volatitle 关键字对于开发人员来说可能比较陌生,但是如果有看过 DCL 的同学应该有见过这个关键字。在上面三个特性中该关键字得到了 2 个特性。volatile 关键字可以保证可见性有程序执行的有序性,如何理解?首先,我们在写完代码后正常的思路确实是程序的执行是自上而下的,这个属于先行发生的原则,但是在线程内是串行,但是在其他线程看来,是没有排序,可能是乱的,也就是不一定执行按照我们书写的逻辑一样自上而下执行。即使如此,虚拟机依旧会保证返回结果的正确性。

       代码示例:

public class Test {
    private int a;  //普通变量
    private volatile boolean b =false ;  //volatile修饰的变量

    public int getA()     //获取变量值
    {
        return this.a;
    }
    public void setA(int a) {   //设置变量值
        this.a = a;
    }

    public boolean isB() {
        return b;
    }

    public void setB(boolean b) {
        this.b = b;
    }
    /**
     * 这代码是没有意义的,仅供理解
     */
    public int testMethod(){
        int  j = 10;
        int i =j;
        a=j;
        //这里有一层屏障
        b = true;
        //这里有一层屏障
        j=a;
        i=j;
        return a;
    }
}
  •   volatile 的有序性:

      上面讲到,volatile 关键字是有有有序性的规则的,后面又说执行是乱序的,那 volatile 关键字是如何保证有序性的。看上面的代码,我们的 volatile 修饰的是中间那段代码,在该关键字前面的代码不会跑到 volatitle 关键字后面去执行,但是,在该关键字前面的代码依旧可以乱序执行。关键字后面的代码也类似,具体理解往下看即可。

      JMM 的内存屏障插入策略: 

            ① 在 volatile 写操作前后各加一层内存屏障,

            ② 在 volatile 读操作后加入 2 层内存屏障。

       如下图:

                                         volatile 写操作的屏障

                                     

                                  volatile 读操作的屏障

                                      

 

  •   volatile 的可见性:

        根据上面的学习,我们知道普通变量是没办法保证修改后立即同步到主内存中的。而 volatitle 修饰的变量又是如何保证可见性的呢?

        我们先看看普通变量与 volatitle 变量的程序执行图。

                普通变量:

                                

                 volatitle 变量: 

                           

          注意:前面提到线程有自己的工作内存,从主内存中拷贝来需要使用的变量副本,变量副本的操作都是在工作内存中完成的,最后才是回写到主内存中。

          而 volatile 关键字修饰的变量会将本地内存无效化,强制需要去主内存中拿,保证了变量每次取到都是最新。这样就保证了 volatile 变量的可见性。

         

volatile 变量存在弊端:

          volatile 在上面讲了可以保证可见性与有序性,但是有一点要注意 volatile 并不能保证原子性。举个例子:java 中的运算,即使是 volatile 变量,也依旧会造成数据不一致(原因:java 的运算并非是原子性操作,导致 volatile 变量的运算在并发下出现不安全现象)

          如下代码:

public class VolatitleTest {

    public static volatile int rece = 0;

    public static void increase(){
        rece++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[20];
        for (int i=0 ;i<20 ; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    //循环累加 1000 共进行 20 次
                    for (int j = 0 ;j<1000;j++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        Thread.sleep(10000);
        System.out.println("得到的值是:"+rece);
    }
}

          运行结果如下图:

                                    

           按道理,我们得到的值应该是 20000 才对,为什么少了?

          我们知道变量 ++ 是如下操作:

         (1) 获取变量值

         (2) 将获取变量 +1

         (3)写回主内存

           在第一步时,我们获取的变量都是最新的,没问题,但是第二步骤,第三步骤都不是原子呀。举个例子:线程 A 和线程 B 都拿到了 rece = 0,A 和 B 分别在 CPU 中自加,最后写回主内存,好像没错,可是两个线程都是同一个值写回主内存,这时就总值就变少了。

 

synchronized 关键字

       这个关键字就相信大家很常见了,也正是因为简单常见,容易被程序员滥用。这篇先不解释该关键字,准备用一片新博文来整理 java 中的各种锁。

 

final 关键字

        final 关键字比较简单,final 关键字的可见性是指:被 final 修饰的字段在构造器中一旦初始化完成,并且构造器没有把 “this” 的引用传递出去(this 逃逸),那么其他线程可以立即看到 final 的字段值。

 

先行发生原则:

      java 内存模型下有一些先行发生的关系,这些先行发生关系不需要任何同步器(锁之类的)就已经存在的,一般来说存已某些关系的,并且可以推倒出来的就会发生先行发生规则。

        可以发生先行发生规则的:

        ①  程序次序规则:在一个线程内,可以理解为就是我们编写的代码逻辑,也就是我们书写的代码顺序。

        ②  volatile 变量原则:volatitle 写操作先行发生与这个变量的读操作

        ③  传递性:如果操作 A 先行发生于操作 B,操作 B 先行发生与操作 C ,可以得出 A 先行与 C。这里就像我们的公式: a= b,b=c,所以得出 a=c。

        ④  锁规则:unlock 操作先行与 lock 操作,且必须是同一把锁。

        剩下的就是一些线程的规则了,这里不描述。

        注意:时间先后顺序和先行发生原则是基本没有太大的关系,平时开发注重先行发生原则即可,不用太过于去考虑时间的先后顺序。

总结:

        这篇博客写的时间有点长了,但是还是收获满满,以为看了很多遍书,应该可以写出来。结果在下笔的时候老是发现不知道如何表达比较好,考虑举例子也是个心累的事。加油,干就对了,努力一把,准备拼一把秋招。祝大家学习进步,工作顺利,熬过这个互联网寒冬期。最后,感谢大家的阅读。

                                                                                  

程序人生,与君共勉~!

 

近期刚刚开始在做公众号:公众号的定位是全方面,包括投资理财,技术,还有可能会分享一些设计作品。如果有兴趣的朋友欢迎关注公众号。

              

 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博删除。 6、可私信博看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博删除。 6、可私信博看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博删除。 6、可私信博看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通;、 3本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看ReAdmE.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博私信或留言,博看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值