zoukankan      html  css  js  c++  java
  • Java内存模型

            先从计算机的硬件效率说起,CPU的计算速度比内存快几个数量级,为了平衡CPU和内存之间的矛盾,引入的高速缓存,每个CPU都有高速缓存,甚至是多级缓存L1、L2和L3,那么缓存与内存的交互需要缓存一致性协议。那么最终处理器、高速缓存、主内存的交互关系如下:

     

        Java的内存模型(Java Memory Model,简称JMM)也定义了线程、工作内存、主内存之间的关系,非常类似于硬件方面的定义:

      

    Java虚拟机运行时内存的区域划分

        方法区:存储类信息、常量、静态变量等,各线程共享

        虚拟机栈:每个方法的执行都会创建栈帧,用于存储局部变量、操作数栈、动态链接等,虚拟机栈主要存储这些信息,线程私有

        本地方法栈:虚拟机使用到的Native方法服务,例如c程序等,线程私有

        程序计数器:记录程序运行到哪一行了,相当于当前线程字节码的行号计数器,线程私有

        堆:new出的实例对象都存储在这个区域,是GC的主战场,线程共享。

        所以对于JMM定义的主内存,大部分时候可以对应堆内存、方法区等线程共享的区域,这里只是概念上对应,其实程序计数器、虚拟机栈等也有部分是放在主内存的,具体看虚拟机的设计。

        线程A、B同时去读取主内存的count初始值存放在各自的工作内存里,同时执行了自增操作,写回主内存,最终得到了错误的结果。

    造成这个错误的本质原因:

        (1)、可见性,工作内存的最新值不知道什么时候会写回主内存

        (2)、有序性,线程之间必须是有序的访问共享变量,我们用“视界”这个概念来描述一下这个过程,以B线程的视角看,当他看到A线程运算好之后,把值写回之内存之后,马上去读取最新的值来做运算。A线程也应该是看到B运算完之后,马上去读取,在做运算,这样就得到了正确的结果。

    接下来,我们来具体分析一下,为什么要从可见性和有序性两个方面来限定。

        给count加上volatile关键字,就保证了可见性。

    private volatile int count = 0;

        volatile关键字,会在最终编译出来的指令上加上lock前缀,lock前缀的指令做三件事情

        (1)、防止指令重排序(这里对本问题的分析不重要,后面会详细来讲)

        (2)、锁住总线或者使用锁定缓存来保证执行的原子性,早期的处理可能用锁定总线的方式,这样其他处理器没办法通过总线访问内存,开销比较大,现在的处理器都是用锁定缓存的方式,在配合缓存一致性来解决。

        (3)、把缓冲区的所有数据都写回主内存,并保证其他处理器缓存的该变量失效

        既然保证了可见性,加上了volatile关键词,为什么还是无法得到正确的结果,原因是count++,并非原子操作,count++等效于如下步骤:

       (1)、 从主内存中读取count赋值给线程副本变量:

                temp=count

        (2)、线程副本变量加1

                temp=temp+1

        (3)、线程副本变量写回主内存

                count=temp

        就算是真的严苛的给总线加锁,导致同一时刻,只能有一个处理器访问到count变量,但是在执行第(2)步操作时,其他cpu已经可以访问count变量,此时最新运算结果还没刷回主内存,造成了错误的结果,所以必须保证顺序性。

        那么保证顺序性的本质,就是保证同一时刻只有一个CPU可以执行临界区代码。这时候做法通常是加锁,锁本质是分两种:悲观锁和乐观锁。如典型的悲观锁synchronized、JUC包下面典型的乐观锁ReentrantLock。

        总结一下:要保证线程安全,必须保证两点:共享变量的可见性、临界区代码访问的顺序性。

    文章来源:https://my.oschina.net/u/1778239/blog/1610185

  • 相关阅读:
    Maven相关知识片
    fastjson使用
    Block pool ID needed, but service not yet registered with NN java.lang.Exception: trace 异常解决
    JVM之内存结构详解
    ContextCleaner ——Spark 应用程序的垃圾回收器
    重新认识Java 8的HashMap
    HDFS NameNode内存全景
    HDFS的NameNode与SecondaryNameNode的工作原理
    大数据——基础知识
    HDFS数据定期清理
  • 原文地址:https://www.cnblogs.com/ryjJava/p/9448330.html
Copyright © 2011-2022 走看看