Java内存模型

为了屏蔽各种硬件和操作系统的内存访问差异,实现Java在不同平台下都能达到一致的内存访问效果,而定义出的一种内存模型规范。

一、主内存和工作内存

Java内存模型的主要目标是为了定义程序中各个变量的访问规则(虚拟机中读写变量....这些变量包括实例字段、静态字段、构成数组对象的元素,但不包括线程私有而不存在竞争的方法参数和局部变量)。Java内存模型并没有现在执行引擎使用处理器的特定寄存器或者缓存来和主内存进行交互,也没有限制编译器调整代码顺序执行这一类优化措施。

Java内存模型规定所有变量都存储在主内存中,除此之外,每条线程拥有自己的工作内存(线程的工作内存中保存的是执行时候需要使用的变量从主内存中拷贝的副本),且线程执行的时候对变量的读写操作都是在自己的工作内存中进行,而不是直接从主存中读或者写。(不同的线程之间不能访问彼此的工作内存,线程之间的访问通信均需要通过主内存来完成)

二、内存建交互操作

1、上面区分了主内存和工作内存二者的关系和区别,那么实际上JMM中定义了下面几种操作来完成变量从主内存拷贝到工作内存、然后从工作内存同步写回主内存中。而且这些操作都是原子性的(64位的double类型和龙类型可能会被拆分成32位来进行操作)

①lock(锁定):作用与主内存中的变量,将一个变量标识为线程独占的状态;

②unlock(解锁):作用于主内存中的变量,将一些被线程独占锁定的变量释放,从而可以被其他线程占有使用;

③read(读取):作用于主内存中的变量,将变量的值从主内存中传输到工作内存中,以便后去的load操作使用;

④load(载入):作用于工作内存中的变量,将上面read操作从主内存中获取的值放在自己的工作内存中的变量副本中;

⑤use(使用):作用于工作内存中的变量,将工作内存中的变量值传递给执行引擎,当虚拟机需要使用变量的值的时候回执行这个操作;

⑥assign(赋值):作用于工作内存中的变量,将一个从执行引擎接受到的值赋给工作内存中的该变量,当虚拟机遇到给变量赋值操作的指令时候执行;

⑦store(存储):作用域工作内存中的变量,将工作内存中的变量值传送回主内存中,以便后续的write操作使用;

⑧write(写入):作用于主内存中的变量,将store操作从工作内存中得到的变量的值写回主内存的变量中。

2、对于一个变量而言:如果要从主内存复制到工作内存,就需要顺序执行read和load操作;如果需要将其写回主内存,就需要顺序的执行store和write操作。(这两种情况是需要按序执行的,但是不限制必须连续执行,在他们操作之间可以执行其他指令)

3、一些其他的规则

①不允许read和load、store和write操作中的一个单独出现(即不允许将一个变量从主内存中读取但是工作内存不接受、或者是从工作内存写回但是主内存不接受的情况);

②不允许一个线程丢弃最近使用的assign操作(即变量在工作内存中改变了之后需要将变化同步回主内存之中);

③不允许一个线程在没有发生assign操作的时候,就将数据从工作内存同步回主内存之中;

④一个新的变量只能在主内存之中产生,不允许在工作内存中直接使用一个未被初始化的变量(对这个变量执行load或assign操作),即对一个变量执行use和store操作之前必须执行了assign和load操作;

⑤如果对一个变量进行lock操作,将会清空工作内存中该变量的值,在执行引擎使用这个变量之前,需要重新执行load和assign操作;

⑥如果一个变量事先没有被lock操作锁定,那么不允许对其执行unlock操作,也不允许某一个线程去unlock一个被其他线程lock住的变量;

⑦在对一个变量执行unlock之前,必须将这个变量的值同步回主内存中。

三、volatile变量

1、volatile是JVM提供的轻量级同步机制

当一个变量被定义为volatile之后,会具备下面两种特性

a)保证此变量对于其他所有线程的可见性(当某条线程改变了这个volatile的值后,其他的线程能够得知这个变化)。这里需要指出:

①volatile变量存在不一致的情况(虽然各个线程中是一致的,但是这种情况的原因是在使用变量之前都需要刷新,导致执行引擎看不到不一致的情况,那么在各个线程中看到的自然就是一致的);

②Java中的运算不是原子的,导致基于volatile变量在并发情况下的操作不一定就是安全的,如同下面的例子:使用10个线程对volatile类型的变量count进行自增运算操作,然后观察运行的计算结果发现并不是期望的100000,而是小于该值的某个其他值

1package cn.test.Volatile; 2 3import java.util.ArrayList; 4import java.util.List; 5 6publicclass TestVolatile02 { 7volatileintcount = 0; 8void m(){ 9count++;10    }1112publicstaticvoid main(String[] args) {13finalTestVolatile02 t =new TestVolatile02();14List threads =newArrayList<>();15for(inti = 0; i < 10; i++){16threads.add(newThread(new Runnable() {17                @Override18publicvoid run() {19for(inti = 0; i < 10000; i++){20                        t.m();21                    }22                }23            }));24        }25for(Thread thread : threads){26            thread.start();27        }28for(Thread thread : threads){29try {30                thread.join();31}catch (InterruptedException e) {32// TODO Auto-generated catch block33                e.printStackTrace();34            }35        }36        System.out.println(t.count);37    }38}

③实际上使用javap反编译之后的代码清单,我们查看m()方法的汇编代码,发现count++实际上有四步操作:getfield->iconst_1->iadd->putfield,分析上面程序执行和预期结果不同的原因:getfield指令把count值取到栈顶的时候,volatile保证了count的值在此时是正确的,但是在执行iconst_1和iadd的时候,其他线程可能已经将count的值增大了,这样的话刚刚保存在操作数栈顶的值就是过期的数据了,所以最后putfield指令执行后就可能把较小的值同步到主存当中了。

void m();

Code:

0: aload_0

1: dup

2: getfield      #2// Field count:I5: iconst_1

6: iadd

7: putfield      #2// Field count:I10:return

④上面的代码要想保证执行正确,我们还需要在执行m方法的时候使用锁的机制来保证并发执行的正确性,可以使用synchronized或者并发包下面的原子类型。下面需要使用这两种加锁机制的场合

□运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

□变量不需要与其他的状态变量共同参与不变约束。

⑤看下面的代码,就比较适合volatile。使用volatile类型的变量b能够在其他线程调用endTest修改b的值之后,所有doTest的线程都能够停下来。

1package cn.test.Volatile; 2 3import java.util.concurrent.TimeUnit; 4 5publicclass TestVolatile01 { 6volatilebooleanb =true; 7 8void doTest(){ 9System.out.println("start");10while(b){}11System.out.println("end");12    }1314void endTest() {15b =false;16    }1718publicstaticvoid main(String[] args) {19finalTestVolatile01 t =new TestVolatile01();20newThread(new Runnable() {21            @Override22publicvoid run() {23                t.doTest();24            }25        }).start();2627try {28TimeUnit.SECONDS.sleep(1);29}catch (InterruptedException e) {30            e.printStackTrace();31        }32        t.endTest();33    }34}

b)禁止指令重排序优化

普通变量只能保证在程序执行过程中所有依赖赋值结果的地方都能获取到正确的结果,但是不能保证变量赋值操作的顺序与程序中的代码执行顺序一致。而指令的重排序就可能导致并发错误的结果。使用Volatile修饰就可以避免因为指令重排序导致的错误产生。

c)java内存模型中对volatile变量定义的特殊规则。假定T表示一个线程,V和W分别表示volatile型变量,那么在进行read、load、use、assign、store和write操作时需要满足如下规则:

①只有当线程T对变量V执行的前一个动作为load时,T才能对V执行use;并且,只有T对V执行的后一个动作为use时,T才能对V执行load。T对V的use,可以认为是和T对V的load。read动作相关联,必须连续一起出现(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对V修改后的值)。

②只有当T对V的前一个动作是assign时,T才能对V执行store;并且,只有当T对V执行的后一个动作是store时,T才能对V执行assign。T对V的assign可以认为和T对V的store、write相关联,必须连续一起出现(这条规则要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程看到自己对V的修改)。

③假定动作A是T对V实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,假定动作P是和动作F相应的对V的read或write动作;类似的,假定动作B是T对W实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作Q是和动作G相应的对W的read或write动作。如果A先于B,那么P先于Q(这条规则要求volatile修饰的变量不会被指令的重排序优化,保证代码的执行顺序与程序的顺序相同)。

四、原子性、可见性与有序性

1、原子性:由Java内存模型来直接保证的原子性变量操作(包括read,load,assign,use,store,write),可以认为基本数据类型的访问读写都是具备原子性的(尽管double和long的读写操作划分为两次32位的操作来执行,但是目前商用虚拟机都将64位的数据读写操作作为原子性来对待)

2、可见性:当一个线程修改了共享变量的值,其他线程能够立即得到这个修改的状况。除了volatile,Java还有两个关键字能实现可见性,synchronized和final。同步块的可见性是由“对一个变量执行unlock操作之前,必须把此变量同步回主内存中(执行store和write操作)”这条规则获得的,而final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么其他线程中就能看见final字段的值。

3、有序性:Java提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

五、先行发生原则(happens-before)

1、先行发生原则:是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A发生在操作B之前,那么操作A产生的结果能被操作B观察到(这个结果包括修改内存中共享变量的值、发送通信消息、调用某个方法等等)。

2、下面是Java内存模型中的一些先行发生关系,这些happens-before关系不需要任何的同步操作就已经存在,可以在编码中直接使用,如果两个操作之间的关系不在此列,并且无法从下列规则中推导出来的话,他们就没有顺序性保证,那么虚拟机就可以对其进行重排序优化。

①程序次序规则:在一个线程内,按照程序控制流(包括分支、循环)顺序执行。

②管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。

③volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。

④线程启动规则:Thread对象的start方法先行发生于此线程的每个动作。

⑤线程终止规则:线程中的所有操作都先行发生于此线程的终止检测,我们可以通过Thread.join()方法结束/Thread.isAlive()的返回值等手段检测到线程已经终止执行。

⑥线程终端规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

⑦对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于他的finalize方法的开始、

⑧传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。

java内存模型 final_Java内存模型相关推荐

  1. JAVA 继承内存模型_Java内存模型

    JVM的组成 类加载器(classloader) 执行引擎(execution engine) 运行时数据区域(runtime data area) 对于Java程序员来说,在虚拟机自动内存管理机制下 ...

  2. java内存规范_Java内存模型-jsr133规范介绍

    最近在看<深入理解Java虚拟机:JVM高级特性与最佳实践>讲到了线程相关的细节知识,里面讲述了关于java内存模型,也就是jsr 133定义的规范. 系统的看了jsr 133规范的前面几 ...

  3. java float内存结构_Java后端开发岗必备技能:Java并发中的内存模型

    欢迎关注专栏: Java架构技术进阶 .里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦. JMM通过构建一个统一的内存模型来屏蔽掉不同硬件平台和不同操作系统之间的差异,让Jav ...

  4. java并发编程系列-内存模型基础

    java线程之间的通信对程序开发人员是完全透明的,内存的可见性问题很容易困扰很多开发人员.本篇博文将揭开java内存模型的神秘面纱,来看看内存模型到底是怎样的. 并发编程中需要处理的两个关键问题: · ...

  5. java内存模型和内存结构_Java内存模型和优化

    java内存模型和内存结构 总览 许多多线程代码开发人员都熟悉这样的想法,即不同的线程可以对持有的值有不同的看法,这不是唯一的原因,即如果线程不安全,它可能不会看到更改. JIT本身可以发挥作用. 为 ...

  6. Java虚拟机学习 - 体系结构 内存模型(转载)

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...

  7. Java的多线程以及内存模型的知识点梳理,有想到过这些吗?

    JMM大致描述: JMM描述了线程如何与内存进行交互.Java虚拟机规范视图定义一种Java内存模型,来屏蔽掉各种操作系统内存访问的差异,以实现Java程序在各种平台下都能达到一致的访问效果. JMM ...

  8. Java Jvm虚拟机的内存模型概述 《对Java的分析总结》(一)

    <对Java的分析总结>-Java虚拟机的内存模型 ** 你可能需要 CSDN 网易云课堂教程 掘金 EDU学院教程 知乎 Flutter系列文章 头条同步 百度同步 本文章首发于微信公众 ...

  9. java内存管理之内存模型

    1,运行时数据区域 1. 程序计数器 (program counter register) 2. Java虚拟机栈 (jvm stack) 3. 本地方法栈 (native method stack) ...

最新文章

  1. 马化腾:人工智能的“大社交”时代
  2. 1-6 数据查询(下)——复杂查询
  3. Flink从入门到精通100篇(六)-Flink 应用之 对Release 文档进行深度解读
  4. php设计分布图,MySQL分表实现上百万上千万记录分布存储的批量查询设计模式[图]_MySQL...
  5. 服务器mysql如何添加数据库文件,如何在使用MySQL作为嵌入式服务器时创建数据库文件...
  6. 能让你的Intellij IDEA 起飞的几个设置(设置背景 字体 快捷键 鼠标悬停提示 提示忽略大小写 取消单行显示)
  7. node.js中对 redis 的安装和基本操作
  8. android 模拟crash_Android 收集Crash信息及用户操作步骤
  9. MySQL划重点-查询-聚合
  10. Axure 经典实例高保真原型下载(Axure高保真企业办公oa系统OA协同办公后台管理会议管理用户管理统计分析活动管理+考勤管理+档案管理+行政支持管理)
  11. Python:学习笔记
  12. 探索科学的奥秘之门Science,Cell, Nature
  13. PHP TP5框架 发送短信验证码
  14. 解决build.gradle文件报错No candidates found for method call xxxxxxx
  15. 仿微信录音功能-(声波动画,上滑取消,超时截取,倒计时提醒)
  16. 内网渗透思路10之SPN拿下域控
  17. Yolov7实战,实现网页端的实时目标检测
  18. OpenCV-Python身份证信息识别
  19. 如何提高或者修改WiFi发射功率
  20. BIO基本介绍以及使用

热门文章

  1. 中国传统的婚姻观,害了男人,也害了女人
  2. 实力派斜杠青年:荣威RX3“开箱”首测
  3. 1.解读DSI——DSI301
  4. 用ts接第三方h5sdk时,简单书写第三方sdk的(.d.ts)声明文件
  5. 联诚发(LCF)全彩LED显示屏,炫彩耀世界
  6. 帮助企业制作帮助文档的6大“黑科技”工具!
  7. 大学解惑09 - 单独用HTML javascript CSS 实现三版99乘法表,你就是班里最靓的仔
  8. 基于SVM的多故障分类器|和车神哥一起学系列
  9. 2018 ICPC 南京 M. Mediocre String Problem (马拉车+扩展kmp)
  10. Jodd发送json