多线程越来越多的使用,使得我们需要对它的深入理解。那么就涉及到了Java内存模型JMM。JMM是JVM的一部分,JMM定义了一个线程修改了一个共享变量,其他线程什么时候或者如何看到这个变量,如何去访问共享变量。

  咱们来看一张图(图片手绘的,字写的不好,见谅),JVM里边分为堆和栈,每一个线程都有一个线程栈,用于区分其他线程。

  每个线程的入口是一个run方法,然后run方法开始调用其他方法。在方法中有两种数据类型,一种是原始类型,一种是引用类型。原始类型如( boolean, byte, short, char, int, long, float, double),这种其他线程根本看不到,只在线程中使用(存放在线程调用栈中)。另外一种是引用类型,引用类型比如原始类型的对象类型,比如(Boolean,Byte等),线程使用的时候会在堆中生成一个对象,并且将变量对其进行指向。每个线程使用此种变量的时候会自己创建一个变量(副本)并且指向,只有当前线程知道,其他线程看不到。那么引用对象的成员变量又分为基本类型和原始类型,并且一次进行创建副本并且指向。

   在方法中用到static对象的实例的需要进行区别对待。因为在堆里边只有一个对象,所有线程对其进行引用。此时不是副本,需要注意。那么如果多线程对其惊醒操作的时候会出现值写的不对的,比如两个线程同时对对象里边的成员变量原始int类型count进行加1,如果count初始化的0的话,可能会出现结果为1的情况。

  那么如果传过来的参数分为基本类型和引用类型呢?如果为基本类型,那么就是副本,如果是引用类型的话,就是原始对象的副本和副本指向,在这时需要注意,如果改了内存中对象的属相,那么随之这个对象会发生改变,但是对象的指向不会改变。

  下面咱们通过代码才详细看一下,看之前首先看看图片。

  

  咱们先进行简单的解释,一个对象,里边有两个方法,第一个方法只有一个原始类型变量,第二个方法有两个变量,一个是原始对象引用类型,另一个是静态对象实例的引用类型。这个图就是他们的内存结构图。下面咱们来看看代码:

package com.hqs.jmm;

/**
*
* JMM对象
* @author qs.huang
*
*/
public class MyJMMObject implements Runnable{ @Override
public void run() {
methodOne();
} public void methodOne() {
int var1 = 0; //方法内部变量,原始类型
methodTwo();
} public void methodTwo(){
Integer var1 = new Integer(0); //方法内部变量,引用变量
MyReferenceObj var2 = MyReferenceObj.instance; //静态引用变量
} }
package com.hqs.jmm;

/**
*
* 引用对象
* @author qs.huang
*
*/ public class MyReferenceObj {
public static MyReferenceObj instance = new MyReferenceObj();
public Integer intObj = new Integer(1);
public int intPrimary = 0;
//隐藏构造
private MyReferenceObj(){}
}

  因为中的var1是方法1的局部变量,也是原始类型,每个用到它的地方,把它放到方法栈中。每一个线程都有自己的栈,所以每个一份。

  方法2中的var1是一个对象类型的也是局部的,每个线程需要在堆里边创建一个对象,同时对它进行指向。

  方法2中var2是一个静态对象实例的引用,所以再堆中只有一份,并且在加载的时候进行的实例化,因为对象中还有引用类型,所以产生了一个对intObj的引用,同时还有一个int原始类型存在堆里,跟随实例对象。

  那么方法中如果有基本类型的数组呢?那数组会在堆中生成,然后对象只想堆中的数组对象,多线程的话,每个线程会生成自己所需要的副本,当方法调用完成后,该数组对象就会被收回。

  下面咱们来看看CPU的硬件结构以便大家更理解JMM。看图:

   目前的电脑一般都是2个或2个以上的CPU,每个CPU可能是多核的。那么每个CPU在同一时间就可以处理一个线程,多个CPU就可以同一时间执行多个线程。

  每个CPU都有一个寄存器,CPU通过寄存器进行运算,那么在寄存器运算速度要高于在主内存进行运算。

  每个CPU都附带一个缓存,用于将数据从主内存中读取到缓存数据中,然后再运算的时候放到寄存器里。CPU访问寄存器的速度是最快的,访问缓存的速度其次,最后是访问主内存的速度,当然缓存分为L1,L2 两个缓存,当然我画的没有那么好,不过不影响理解。CPU不会读取缓存中的所有数据,而是按照缓存line去进行有选择的读取。

  当CPU执行完相关的运算并在适当的时候将结果刷到主内存RAM中,用于保存结果或让其他程序读取。咱们看一下JVM和CPU之间的关系。

  因为CPU没有堆和栈,JVM的堆和栈会在CPU的主内存中,但是程序执行的时候,会将栈或堆中的线程读取到缓存和寄存器中进行运算,并且将计算的结果重新刷新到主内存RAM中。在这个时候因为有多CPU的原因,那么假如说一个CPU一个变量,那么两个并行的线程在执行的时候会有什么样的问题呢?

  比如一个类中只有一个String state的成员变量,一个线程对其进行读取到CPU缓存中,然后将其设置为了'YES',并放回到缓存中;另一个线程没有看到这个值的更改,因为没有看到起更改。然后将其读取到CPU缓存中,然后设置为'YES'或'NO'。这个就是可见性问题,那么如何实现其他线程可见呢?Java有个关键字volatile,这个关键字可以使得操作不写入CPU缓存,直接从主内存读取,更改后直接重写到主缓存中。

  比如这个类有个int count的成员变量,并且初始化值为0,向前面提到的,一个线程读取到这个count到CPU缓存中,另一个线程也把这个count读取到另一个CPU的线程中,那么两个线程放到寄存器计算,分别对其进行加1操作,这个时候都把结果刷新到缓存并且到主内存中。count的结果变为了1,这个不是大家想要的。因为每个线程对这个变量读取不可见,每个都用其副本进行操作。这个就是线程的竞态条件。那么怎么才能都保证这个变量的正确呢,就是使用同步,也就是使用synchronize关键字或者是锁来进行处理。也就是在同一时间只能有一个线程去处理这个字段或者方法,同时程序也是从主内存读取数据,然后计算完成后将程序写入到主内存中保证保证计算的有序处理。

  这下同学们是否有了新的认识了呢?

  如果有写的不对的地方希望告知~

Java 内存模型- Java Memory Model的更多相关文章

  1. Java内存模型(Java Memory Model,JMM)

    今天简单聊聊什么叫做 Java 内存模型,不是 JVM 内存结构哦. JMM 是一个语言级别的内存模型,处理器的硬件模型是硬件级别,Java中的内存模型是内存可见性的基本保证.从而为我们 volati ...

  2. Java虚拟机12:Java内存模型

    什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致 ...

  3. 全面理解Java内存模型

    尊重原创:http://blog.csdn.net/suifeng3051/article/details/52611310 Java内存模型即JavaMemory Model,简称JMM.JMM定义 ...

  4. 【JVM】JVM内存结构 VS Java内存模型 VS Java对象模型

    原文:JVM内存结构 VS Java内存模型 VS Java对象模型 Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清 ...

  5. 全面理解Java内存模型(JMM)及volatile关键字(转载)

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

  6. Java内存模型学习笔记

    Java内存模型(JMM):描述了java程序中各种变量(线程共享变量)的范根规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节.共享变量就是指一个线程中的变量在其他线程中也是可见 ...

  7. 全面理解Java内存模型(JMM)及volatile关键字

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...

  8. 全面理解Java内存模型(JMM)

    理解Java内存区域与Java内存模型Java内存区域 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁的时机,其中蓝色部分代表的是所有线程共享的数据区 ...

  9. Java内存模型原理总结(转自51CTO)

    转载地址:http://developer.51cto.com/art/201811/587220.htm [51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关 ...

随机推荐

  1. 团队作业4——第一次项目冲刺(Alpha版本) 日志集合处

    第一天(2017.4.23) http://www.cnblogs.com/1413none/p/6752325.html 第二天(2017.4.24) http://www.cnblogs.com/ ...

  2. 个人作业(2)---英语学习APP案例分析

    第一部分 调研, 评测 1.下载并使用,描述最简单直观的个人第一次上手体验. PC上的必应词典主页面与其他英语学习APP类似,一些英文读物的推送,但是每日阅读需要去浏览器去看有点不太方便,我觉得直接在 ...

  3. 201521123093 java 第八周总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 1.泛型简介:同一个代码可以被不同的对象重用 2.使用泛型的好处:允许 ...

  4. 201521123033《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...

  5. 201521123006 《java程序设计》 第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...

  6. 201521123065《java程序设计》第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 finally 题目4-2 1.1 截图你的提交结果(出现学 ...

  7. 201521123114 《Java程序设计》第10周学习总结

    1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 1. 创建线程方式: 定义Thread的子类 定义实现Runnable接口的类,实现run() 2. 调用s ...

  8. php使用ZipArchive压缩文件的心得

    $zip=new ZipArchive; if($zip->open('test.zip',ZipArchive::CREATE)===TRUE){ $zip->addFile('imag ...

  9. 解决vsftp无法启动问题(转)

    [root@node11 ~]# service vsftpd restartShutting down vsftpd:                                      [F ...

  10. 命令导入导出oracle库

    目前还是新手:所以记录下来最笨的方式,留用 一.从服务器先把库导出来 exp sys/mima@orcl  file = "d:\pybghs.dmp"   full=y 二.从服 ...