非要把它讲清楚,Java内存模型(JMM)

在解释 java内存模型 之前,我们先来大概了解一下内存模型。

内存模型是操作系统层的概念,它规定了当cpu执行程序时,如何与内存进行数据交互。
缓存结构

为什么要规定这个东西呢?
因为这个东西很复杂,不好搞。具体来说,我们现代计算机的体系结构中,为了优化cpu的处理效率,在处理器和主存之间往往有多级缓存(例如,L1、L2、L3级缓存)。这就意味着同一个数据可能会出现在多个地方,我们十分希望它们都是一致的,但是往往有很多线程在同时的读写,导致一致性很难保证。我们称此类问题为缓存一致性问题
在多线程并发过程中,可以细化为:如何处理多线程读同步问题与可见性、多线程写同步问题与原子性。

我们回归到正题JAVA内存模型,java 最大的特性就是跨平台,他是运行在java虚拟机(JVM)上的。而JVM 对计算机的内存进行了抽象,规定了哪里是堆,哪里是栈,对象放在哪,变量放在哪,java线程开辟在哪,总之要说的就是JVM内存分区和系统的内存并不一样。而我们要解决java线程面对的缓存一致性问题,就要思考JVM是如何与计算机内存进行数据交互的呢?Java内存模型就是一种JVM的规范,他规定了JVM如何与计算机内存进行数据交互。

以上我们解释了什么是JAVA内存模型,接下来我们需要详细分析几个问题。

  1. JVM 内存分区是怎样的?
    JVM分区
  • 堆(Heap):线程共享。线程不安全。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。

  • 方法区(Method Area):线程共享。存储已被虚拟机加载的类信息、常
    量、静态变量、即时编译器编译后的代码等数据。(注意: JDK8中已经用Metaspace(元数据)区完全替代了永久代(即方法区) 而且元数据区内存不在JVM中。而是使用的本地内存,默认情况下受操作系统内存限制)

  • 方法栈(JVM Stack):线程私有。线程安全。存储局部变量表、操作栈、动态链接、方法出口,对象指针。

  • 本地方法栈(Native Method Stack):线程私有。线程安全。为虚拟机使用到的Native方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。

  • 程序计数器(Program Counter Register):线程私有。线程安全。它可以看作是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令。

  1. JVM 对系统的内存究竟做了怎样的抽象?

在上述图中,已经阐述了一部分JVM对计算机内存的抽象了。我们再来尝试对应一下。

  • 上边我们提到了“堆”是线程间共享的,那么此部分内容对应的空间就应该对应到计算机的主存中,因为对此部分内容操作系统的各个线程均可访问。
  • 不同的java线程有自己私有的工作内存,此部分可对应到cpu的寄存器和高速缓存上来。
  1. JAVA 内存模型又定义了怎样的规则来解决缓存一致性相关的问题?

首先,java内存模型定义了8种操作(以便于定义JVM和计算机内存的交互规则):

  • lock(锁定): 作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁): 作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取): 作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储): 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入): 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

具体规则如下:

  • 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

如此一来和JMM相关的问题我们就大体介绍完了,另外就是我们如何应用这些规则解决的缓存一致性问题以及在我们写代码的时候如何应用这些规则,我们下次再聊。

小小的谈一下感受,关于缓存一致性的问题,其实这个问题很普遍,例如操作系统层面涉及这个问题、JMM也涉及、Redis涉及、Mysql也涉及、大型的主从结构也涉及。不同层面都有不同层面的解决方案,我们应该把它抽象出来成体系的看一下,当我们面对新问题时才有自己解决方案。当然,我整理好了也分享给大家:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从前慢慢慢死了

打钱!一分也行啊!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值