Java——内存模型详解!

这篇具有很好参考价值的文章主要介绍了Java——内存模型详解!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java内存模型是一种抽象的规则或规范,定义了程序中存在竞争现象的对象(包括实例字段、静态字段和数组对象,不包括局部变量,形式参数;后者是线程私有,不存在竞争问题)的访问方式。

        如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。

        JVM通过Java内存模型,屏蔽掉不同硬件和操作系统的内存访问差异,实现各种平台具有一致的并发效果。

1. 内存模型

1.1 硬件的效率与一致性——计算机内存模型

java内存模型,编程,Java,java,开发语言

 

        显然,CPU的运行速度远远大于内存交互速度,至少有这几个数量级的差距。因此,现代计算机在CPU与内存之间都有一个高速缓存(Cache)作为数据缓冲:将运算需要的数据从内存中复制到缓存中,当缓存从CPU中拿到运算结果之后再同步到内存中——这样CPU就不用等待缓慢的内存读写了。

        但此时就出现了一个大问题:缓存一致性。  现在基本都是多核处理器,每个处理器都有自己的高速缓存,而他们共享同一个主内存,这样就会导致各自的缓存数据不一致的情况,为了解决这个问题各个处理器访问缓存时都要遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI、MOSI、Synapse等等。  除此之外,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。类似的,Java虚拟机的即时编译器中也有类似的指令重排序优化。

1.2 Java内存模型

        接下来,我们通过一个Java程序了解缓存一致性。 

        Java的内存模型跟计算机的内存模型极其相似

java内存模型,编程,Java,java,开发语言

 

        CPU在内存模型当中充当的就是“算术逻辑单元”,主要负责运算。  Java线程也可以理解为“运算单元”,因此,可以看到,Java内存模型几乎和计算机内存模型保持一致。

内存模型总述 

1.所有变量都在主内存当中,工作内存中的变量都是从主内存中拷贝的。

2.线程对变量的所有操作都在工作内存中完成

3.不同线程无法直接访问其他线程工作内存中的变量。线程之间的变量传递需要通过主内存完成

        Java中的缓存一致性问题:  .  考虑下面这个代码:  i = 10++;  假设现在有两个线程同时执行上述代码,  根据内存模型总述第1条,这两个线程都是从主内存中拷贝i到自己的工作内存当中,然后分别对其执行自加,然后将其放回主内存。  此时,尽管“自加了两次”,但实际上i=11,而非12。  这种被多个线程访问的变量为共享变量。 

1.3 Java内存模型和计算机内存模型的关系

        通常情况下,当一个cpu需要读取主存的时候它会将主存的部分读取到cpu缓存中。它甚至会将缓存的部分内容读到内部寄存器里,然后在寄存器中执行操作,当cpu需要将结果回写到主存的时候,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

java内存模型,编程,Java,java,开发语言

 

        Heap就是主内存,Thread Stack就是工作内存 

        通过图可以看出java内存模型与硬件架构之间存在一些差异,硬件内存架构它没有区分线程栈和堆,对于硬件而言所有的线程栈和堆都分布在主内存里,部分cpu栈和堆可能出现cpu缓存中和cpu内部的寄存器里面。

2. 内存特性

2.1 原子性

        一个操作是不可中断的,即便是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 

        对基本数据类型的变量读取和赋值的操作都是原子操作,即这些操作是不可中断,要么执行,要么不执行。

x = 10;        //语句1

y = x;        //语句2

x++;          //语句3

x = x + 1;    //语句4

        以上语句只有语句1是原子性操作。

 语句1是直接将数值10赋值给x,也就是说线程执行这个语句会直接将数值10写入到工作内存中。 

语句2实际上包含2个操作,先读取x的值,再将x的值写入到工作内存中,虽然读取x的值及将x的值写入工作内存都是原子操作,但结合起来就不是原子性的操作了。 

x++和x=x+1包含3个操作,读取x的值,进行加1操作,写入新的值。 

也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。 

        Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

2.2 有序性

        编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。  也就是说:在并发时,程序的执行可能会出现乱序。

        另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

2.3 可见性

        一个线程修改了某一个共享变量的值,其他线程是否能够立刻知道这个修改。(可见会牺牲性能)。

        通过volatile关键字保证可见性:

        当一个共享变量被volatile修饰时,它会保证修改的值会立即更新到主存,当有其他线程需要读取时,它会去主存中读取最新的值。

        普通的共享变量不能保证可见性,因为共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取的时候,此时内存中可能还是原来的旧值,因此无法保证可见性。 

        通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。 

2.4 Happen-Before 规则

程序顺序原则:一个线程内保证语义的串行性(按书写次序顺序执行)

锁规则:解锁必然发生在随后的加锁前

传递性:A 先于 B ,B 先于 C,那么 A 必然先于 C

volatile 规则:volatile 变量的写,先发生于读,保证了 volatile 变量的可见性

线程启动规则:线程的 start() 方法先于它的每一个动作

线程中断规则:线程的所有操作先于线程的终结(Thread.join())

线程终结规则:线程的中断先于被中断程序的代码

对象终结规则:对象的构造函数执行结束先于 finalize() 方法

        如果不满足以上规则,则虚拟机就会对其进行指令重排序

指令重排序

        在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序,重排序分为以下三种类型

1、编译器优化的重排序--编译器在不改变单线程程序语义的情况下,可以重新安排语句的执行顺序

2、指令级并行的重排序--如果不存在数据依赖,处理器可以改变语句对应机器执行的语句顺序

3、内存系统的重排序--由于处理器使用缓存和读/写缓存,这使得加载和存储操作看上去像是在乱序执行

        在执行程序时,java内存模型确保在不同的编译器和不同的处理器平台上,来插入内存屏障来禁止特定的编译器重排序和处理器重排序,从而为上层提供内存一致性的条件。

3. 内存同步(交互)的八大操作

        关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下八种操作来完成:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。

unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎。(每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作)

assign(赋值):作用于工作内存的变量,它把一个执行引擎接收到的值赋值给工作内存的变量。(每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作)

store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。

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

同步规则

1、如果要把一个变量从主内存中复制到工作内存,就需要按顺序地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。 

2、不允许read和load、store和write操作之一单独出现 

3、不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。 

4、不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。 

5、一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。 

6、一个变量在同一时刻只允许一条线程对其进行locd操作,但lock操作可以被同一条线程重复执行多次,多次执行load后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。 

7、如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行Load或assign操作初始化变量的值。 

8、如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。 

9、对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。文章来源地址https://www.toymoban.com/news/detail-741499.html

到了这里,关于Java——内存模型详解!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • BsinCopilot:Java版端到端大语言模型开发框架

    BsinCopilot是基于BsinPaaS开源框架构建的大语言模型应用SaaS服务,借鉴langchain的框架思想,引入  langchian4j组件,微前端微服务的架构设计,可快速助您构建和体验端到端的AI应用。 概念 说明 Bsin 毕昇缩写,毕昇,中国北宋发明家,活字印刷术的发明者。毕昇出身平民,长期在

    2024年03月13日
    浏览(57)
  • 玄子Share-自然语言编程(NLP)_Java开发小白向 ChatGPT 提问的最佳模板

    以下内容均为 ChatGPT 回答 玄子: 我向你提问时,问题描述精确的重要性 ChatGPT 3.5 问题描述的精确性非常重要,因为它可以让回答者更好地理解您的问题,并且更容易提供准确和有用的解决方案。如果问题描述不够清晰或不够详细,回答者可能会误解您的问题或者理解不到位

    2023年04月09日
    浏览(47)
  • THRUST:一个开源的、面向异构系统的并行编程语言:编程模型主要包括:数据并行性、任务并行性、内存管理、内存访问控制、原子操作、同步机制、错误处理机制、混合编程模型、运行时系统等

    作者:禅与计算机程序设计艺术 https://github.com/NVIDIA/thrust 2021年8月,当代科技巨头Facebook宣布其开发了名为THRUST的高性能计算语言,可用于在设备、集群和云环境中进行并行计算。它具有“易于学习”、“简单易用”等特征,正在逐步取代C++、CUDA、OpenCL等传统编程模型,成为

    2024年02月07日
    浏览(48)
  • 并发编程——1.java内存图及相关内容

    这篇文章,我们来讲一下java的内存图及并发编程的预备内容。 首先,我们来看一下下面的这两段代码: 下面,我们给出上面这两段代码在运行时的内存结构图,如下图所示: 下面,我们来具体的讲解一下。 首先,我们写了一个java程序是以.java的文件形式保存在磁盘中的,

    2024年02月07日
    浏览(38)
  • Java 内存模型

    JVM 内部使用的 Java 内存模型, 在逻辑上将内存划分为  线程栈 (thread stacks)和 堆内存  (heap)两个部分。 如下图所示:   JVM 中,每个正在运行的线程,都有自己的线程栈。 线程栈包含了当前正在执行的方法链/调用链上的所有方法的状态信息。 原始数据类型和对象引用

    2024年02月04日
    浏览(17)
  • 7. Java 内存模型

    Java 内存模型(Java Memory Model)的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节 Java 内存模型规定了 所有的变量都存储在主内存 (Main Memory)中(虚拟机内存的一部分)。 每条线程还有自己的工作内

    2024年02月04日
    浏览(24)
  • Java 内存模型深度解析

    优质博文:IT-BLOG-CN 【1】并发中常见的两个问题: 线程之间如何通信及线程之间如何同步 。通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种: 内存共享和消息传递 ; 【2】在共享内存的并发模型里,线程之间共享程序的公共状态,通

    2024年01月21日
    浏览(33)
  • Java学习18(Java内存区域详解)

    对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不

    2024年02月01日
    浏览(19)
  • 了解JAVA内存模型(JMM)

    我们常说的JMM指的是Java内存模型(Java Memory Model,JMM),主要用于控制Java程序解决线程间如何通信和数据同步,JMM规范了多线程访问共享内存时的 可见性、有序性和原子性 。 所有的共享变量都存在 主内存 中; 每个线程 都保存了一份该线程使用到的 共享变量的副本 。 如

    2024年02月06日
    浏览(38)
  • Java内存模型之重排序

    首先来看一个代码案例,尝试分析一下 x 和 y 的运行结果。 经过分析可知,线程 one 的第 33 行和第 34 行和线程 two 的第 47 行和第 48 行是核心代码,这 4 行代码的执行顺序决定了最终 x 和 y 的结果。 根据大多数人对多线程的认知,在线程 one 内部,第 33 行和第 34 行代码的执

    2024年02月02日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包