原创 Java小白系列(六):JMM(Java Memory Model)

发布时间:2021-08-02 18:11:01 浏览 205 来源:猿笔记 作者:小小青叶

    JVM为程序提供了系统无关的统一的API,我们将深入学习JVM的内存模型,它定义了不同线程对于一个共享变量的读写是如何可见的:以及需要时如何同步访问共享变量,线程间通过读/写共享对象进行隐式通信,线程间通过发送消息来显示的进行通信,必需显示指定某个方法或某段代码块需要在线程间互斥执行,JMM规定了内存主要分为主内存(物理内存)和线程工作内存(缓存、寄存器、高速缓冲区、以及硬件和编译器优化后的一个数据存放位置),1JMM规定了所有变量都存储在主内存中,工作内存中存储着共享变量的主内存副本,主内存对应的是Java堆中的对象实例域、静态域和数组元素。


    # #一、前言

    其实,本篇应该在[《小白五:volatile》](

    Java之所有流行,是因为Java程序能够跨平台运行,而最核心的就是JVM(JavaVirtualMachine)了。

    那么我想问:JVM的核心是什么?

    JVM为程序提供了一个统一的独立于系统的API,同时还管理每个程序的内存分配和恢复。在本文中,我们将研究JVM的内存模型:JMM。

    JMM是Java内存模型,屏蔽不同的操作系统和不同的硬件厂商,提供统一的内存管理。

    JMM在JDK1.5之前表现不好,自从JDK1.5和JSR-133发布以来,新JMM车型一直沿用至今!

    JMM非常重要。只有当你熟悉了JMM,你才能很好地、正确地开发并行程序。它定义了不同的线程如何读写共享变量,以及如何在必要时同步访问共享变量。

    # #二。并发编程的关键问题

    ###2.1.线程通信

    这是一个老生常谈的话题,一般有两种线程通信:

    -内存共享

    -消息

    -在共享内存的并发模型中,线程通过读/写共享对象进行隐式通信;

    -在消息传递的并发模型中,线程通过发送消息进行通信,比如:wait/notify;;

    ###2.2.线程间同步

    同步是指程序用来控制不同线程之间操作相对顺序的机制。

    共享内存的并发模型中,同步是显式的,必须表明某个方法或某个代码块需要在线程间互斥;

    在消息传递的头部模型中,消息必须在收到之前发送,因此同步是隐式的;

    ##三、JMM内存划分

    如下图所示,JMM规定内存主要分为主内存(物理内存)和线程工作内存(缓存、寄存器、缓存和一个由硬件和编译器优化的数据存储位置)

    JMM规定所有变量都存储在主存储器中!每个线程也有自己的工作内存(本地内存),共享变量的主内存副本存储在其中。

    主内存对应Java Heap中的对象实例字段、静态字段和数组元素,所以堆内存在线程间共享。

    局部变量、方法参数和异常处理程序参数不在线程间共享,也不受JMM的影响,因此它们对内存是不可见的。我们也称它们为线程堆栈(方法调用是堆栈的,方法变量也在堆栈上,方法在退出时弹出)。

    由此我们也可以看出,JMM并不是真的存在,它只是定义了线程和主存之间的抽象关系。

    ###3.1、从JVM角度来理解JMM(内部JMM)

    我们可以看到,在JVM中,JMM被分成两部分:线程堆栈和堆。这也证实了我们上面说的,Heap是线程间共享的,而stack是线程独有的。为什么叫ThreadStack?正如我在上一节中所说的,调用方法意味着堆叠。即使两个线程运行相同的代码,也会创建自己的栈,栈是->创建局部变量->弹出。

    * *基本类型的所有局部变量都完全存储在线程堆栈中,因此对其他线程完全不可见。**

    * *一个线程可以将基本类型变量的副本传递给另一个线程,但不能将基本类型变量共享给另一个线程!**

    上图是更为详细的JMM在JVM中。

    局部变量:

    -如果是基本类型变量,则100%存储在线程堆栈上,对其他人不可见;

    -如果是引用对象变量,变量存储在线程栈上,但被引用对象本身存储在堆上;

    -对象可能包含方法,这些方法可能包含局部变量,这些变量也存储在线程堆栈上,即使对象本身存储在堆中。

    对象的成员变量存储在堆上,即使成员变量是基本类型或引用对象类型;

    静态类变量也存在于堆上;

    所有线程都可以访问堆上的对象。当一个线程访问一个对象时,它也可以访问该对象的成员变量。如果两个线程同时调用同一个对象的同一个方法,那么它们会同时访问对象的成员变量,但是每个线程实际上访问的是对象的成员变量的一个副本。

    上图中,两个线程的『methodOne』的『Localvariable2』都引用堆中的同一个『Object3』,同时注意,『Object3』又将『Object2』和『Objecet4』作为其成员变量而引用,因此,两个线程都能通过『Object3』来访问其成员变量『Object2』和『Object4』。

    上图还表明“methodTwo”的“局部变量1”是指不同的对象,即“对象1”和“对象5”。理论上,两个线程都可以访问“对象1”和“对象5”,但是上图显示了不同的情况。Java代码如何实现才能让它们在内存中不一致?

    //线程

    publicclassMyRunnableimplementsRunnable(){

    publicvoidrun(){

    methodOne();

    }

    publicvoidmethodOne(){

    //基本类型变量只会在线程堆栈上,因为它不是对象,所以不会在堆上分配内存

    intlocalVariable1=45;

    MySharedObjectlocalVariable2=MySharedObject.sharedInstance;

    //...domorewithlocalvariables.

    methodTwo();

    }

    publicvoidmethodTwo(){

    IntegerlocalVariable1=newInteger(99);

    //...domorewithlocalvariable.

作者信息

小小青叶 [等级:3] 大前端高级技术专家
发布了 42 篇专栏 · 获得点赞 182 · 获得阅读 9635

相关推荐 更多