首页 > 其他分享 >JMM说明

JMM说明

时间:2023-05-04 21:14:01浏览次数:54  
标签:变量 lock 说明 线程 内存 JMM 操作 before

JMM(Java内存模型)是一种定义了多线程之间共享数据、以及数据读写时的可见性和有序性的规范。JMM规范是建立在操作系统内存模型之上的,是Java语言对于并发编程的一种抽象,规范了Java程序在并发情况下内存访问的行为。

Java内存模型主要包含以下几个概念:

  • 主内存:Java虚拟机中的主内存是所有线程共享的内存区域,所有变量的值都存储在主内存中。

  • 工作内存:Java虚拟机中的每个线程都有自己的工作内存,工作内存中存储了主内存中的变量副本。

  • 内存间交互操作:Java虚拟机定义了一组原子性的操作,用于实现线程之间的内存交互,例如lock、unlock、read、write等。

  • Happens-before关系:用于描述程序中操作之间的顺序关系,如果一个操作在happens-before于另一个操作,那么在程序中这两个操作是有顺序的。

Java内存模型主要是为了保证多线程之间的内存可见性和有序性。在Java中,多个线程可以同时访问共享的变量,如果不采取一些措施,就会出现内存读写不一致的问题。Java内存模型通过保证线程之间的内存可见性和有序性,从而避免了这些问题的发生。

总的来说,Java内存模型规范了Java程序在并发情况下内存访问的行为,包括变量的可见性、指令的有序性等,对于理解Java并发编程非常重要。

什么是主内存?什么是本地内存?

主内存: 所有线程创建的实例对象都存放在主内存中,不管该对象是 成员变量 还是 局部变量.
本地内存: 每个线程都有自己的私有本地内存用来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存.本地内存是JMM抽象出来的概念,存储了主内存中的共享变量副本.

注意这里讲的是本地内存,但是有的资料讲的是工作内存,感觉工作内存这个概念更加符合,本地内存让我想到JVM运行时数据区的模型了.
找了一圈,没找到在哪里讲到, 暂时可以把本地内存和工作内存看作是一样的吧
JSR133规范
可以看到JSR133规范并没有讲到什么主内存,本地内存或者工作内存,那么几个概念是在哪里给出来的呢? 不可能由博主自己想出来的,肯定有一个出处.
JDK8文档
暂时在这里也没有找到关于工作内存的说明
Java虚拟器规范
三个文档都没有找到明显关于本地内存或者工作内存的说明,这就比较奇怪了.

主内存与工作内存的交互过程

一个变量如何从主内存拷贝到工作内存, 如何从工作内存同步到主内存当中? JMM定义了以下8种同步步骤

  1. 锁定lock: 作用于主内存中的变量,标志该变量为一个线程独享变量
  2. 读取read: 作用于主内存的变量,就是把变量从主内存读取到工作内存当中.
  3. 载入load: 把read操作得到的变量放入到工作内存的变量副本中.也就是完成工作内存的共享变量副本的copy操作.
  4. 使用use: 把工作内存中的一个变量的值传递给执行引擎,就是虚拟机使用该变量时,都会调用该指令.
  5. 赋值assign: 作用于工作内存中的变量,把一个从执行引擎接收到的值赋给工作内存中的变量.
  6. 存储store: 作用于工作内存的变量,就是把工作内存的变量传送到主内存当中.
  7. 写入write: 作用于主内存的变量, 就是把从工作内存得到的变量写入到主内存当中.
  8. 解锁unlock: 作用于主内存中的变量,只能解锁的变量,才能被其他线程占用.

上面是JMM内存模型的一个工作过程,可以结合图片来看, 不需要硬背这个过程,因为它的基本流程还是比较清楚的.
除了上面的8个同步步骤,还规定了一些规则用来保证这些同步操作正确执行.

  1. 不允许一个线程不经过assign操作就直接把变量同步到主内存当中,也就是直接执行store操作.

想想也是,如果线程只是读取共享变量,没有进行赋值操作,完全没必要更新主内存的变量值.

  1. 一个新的共享变量只能在主内存诞生,不允许直接在工作内存当中使用未被初始化的变量.

我们可以这么理解,当一个变量在主内存初始化了,如果一个线程使用到该变量,就需要从主内存read和load变量到工作内存, 从而保证线程间使用的变量是相同的, 如果直接在工作内存对变量进行初始化, 因为主内存并没有对应的值,这样多线程可能会同时进行初始化,导致变量的不一致性.

  1. 一个变量在同一时刻只允许一个线程对其lock,但是lock操作可以被同一个线程多次执行, 多次执行lock操作,之后也需要多次执行相同的unlock操作

为什么lock操作可以被同一个线程多次执行? 什么场景下需要多次lock操作呢?
一个线程可能会多次lock操作是因为在其执行期间可能存在多个临界区,每个临界区都需要对该变量进行保护。例如,在生产者-消费者模型中,生产者和消费者都需要对共享Buffer进行保护,因此每个线程在进入临界区之前需要先执行lock操作以确保只有一个线程可以访问共享资源。如果多个临界区相互嵌套,那么同一个线程可能会多次进行lock操作以保证每次访问共享资源都是安全的。此外,如果某个线程拥有该变量的lock,在多次进入临界区时,它可以重复执行lock操作,以保证它依然拥有该变量的lock。当这个线程结束其临界区操作时,需要与之匹配的是相同数量的unlock操作,以确保该变量的lock可以被其他线程获得。
注意这里讲的是多次lock操作,并不是指多次对同一个变量进行lock操作

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

这里清空工作内存中的变量值,是指所有线程的值,还是执行lock操作的线程的工作内存呢?
这种清空操作为什么在8个同步步骤当中没有显示出来.
对这条规则有所怀疑

  1. 如果一个变量事先没有执行lock操作,则不能直接对其进行unlock操作,也不能对其他线程lock的变量进行unlock.
  2. 对一个变量时行unlock前,必须先将变量刷新到主内存当中.

Java内存区域与JMM有何区别?

Java内存区域, 一般指Java运行时的内存区域,主要定义了JVM运行时如何进行数据存储
JMM与java并发编程有关,是一套保证并发编程的可见性和有序性的规范.它抽象了线程和主内存之间的关系,规定了从java源代码到CPU可执行指令的这个转化过程需要遵守哪些和并发相关的原则和规范,主要目的是为了简化并发编程,增强程序可移植性.

happen-before原则

happen-before原则的设计思想

  1. 为了对编译器和处理器的约束尽可能少,只要不改变程序的执行结果,编译器和处理器怎样进行指令重排都可以
  2. 对于会改变程序执行结果的指令重排,JMM要求编译器和处理器必须禁止这种重排.

heppen-before原则定义

  1. 如果一个操作happen-before另一个操作,那么第一个操作的执行结果对第二个操作可见,并且第一个操作的执行顺序在第二个操作之前.
  2. 两个操作间存在happen-before关系,并不意味着java平台的具体实现必须按照happen-before关系指定的顺序执行,如果重排序之后的执行结果,与按happen-before关系来执行的结果一致,那么JMM也允许这样的重排序

怎么感觉第一原则与第二原则有矛盾, 两个操作间存在happen-before关系,无非就是A操作happen-beforeB操作,或者B操作happen-beforeA操作.
根据第一原则,就肯定A操作在B操作之前执行,或者B操作在A操作之前执行,这是happen-before关系指定的顺序,但是JMM规定如果指令重排后,执行结果一致,那么这种重排也允许的,就是是JMM把happen-before放得更加宽了.

例子

int a = 1; //A操作
int b = 2; //B操作
int c = a + b;//C操作

请看上面的例子:A操作happen-before B操作,B操作happen-before C操作, A操作happen-before C操作,对于A操作和B操作,其实存在happen-before关系,但是重排序后对执行结果没有影响,所以JMM允许这种重排序.

happen-before原则

  1. 程序顺序规则:其实就是在同一个线程当中,程序按顺序执行
  2. volatile变量规则:对一个volatile变量的写操作 Happens-Before 于任何后续对该变量的读写操作。其实就是volatile变量写操作优先
  3. 传递性:如果操作A Happens-Before 操作B,操作B Happens-Before 操作C,则操作A Happens-Before 操作C。
  4. synchronized锁规则:一个unlock操作 Happens-Before 于之后对同一个锁的lock操作。

其实就是如果想对一个已经lock的变量再次lock的话,需要先unlock操作
happen-before可以理解成发生在什么操作之前,所以上面应该解读成一个unlock操作发生在之后对同一个锁的lock操作之前

  1. 线程启动规则:Thread对象的start方法 Happens-Before 于启动的线程执行任何操作。
  2. 线程终止规则:线程的所有操作 Happens-Before 于其他线程检测到该线程已经终止。

其他线程检测到该线程已经终止之前,线程的所有操作应该已完成

  1. 线程中断规则:对线程调用interrupt方法 Happens-Before 于被中断线程的执行被响应。
  2. 对象终结规则:一个对象构造函数的结束 Happens-Before 于该对象的 finalize 方法的开始。

这些规则帮助Java程序员了解在多线程编程中操作之间的时间顺序关系,并且确保操作发生的正确顺序。遵循这些规则可以避免一些常见的多线程问题,如死锁、竞态条件和数据损坏等。

happen-before与JMM的关系

一个happen-before规则对应于一个或多个编译器和处理器重排序规则.

并发的三个重要特性

原子性: 一次操作或者多次操作的时候,要么所有操作都一起成功,要么所有操作一起失败, 通过sychronized,lock以及各种原子类实现来保证
有序性: 由于指令重排序问题,代码的执行顺序未必就是代码的书写顺序, 通过volatile关键字保证
可见性: 当一个线程对某个共享变量进行修改, 其他线程立马可以收到最新的值, 通过sychronized, lock, volatile来保证

标签:变量,lock,说明,线程,内存,JMM,操作,before
From: https://www.cnblogs.com/liveinworld/p/17372499.html

相关文章

  • C++获取阿里巴巴1688中国站店铺详情 API 接口返回值示例说明
    ​C++(cplusplus)是一种计算机高级程序设计语言,由C语言扩展升级而产生,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。......
  • 16 春节快乐|一篇暂停更新的说明
    你好呀,我是辰洋,《郭东白的架构课》的负责人。我又来啦!不过这一次,我不是带着加餐来的,而是带着一套“减餐”。是的,专栏将在春节期间暂停更新。临近春节,各种不可控因素打乱了东白老师的写作计划,相应地也打乱了我们的备稿节奏,这让我们暂时无法保证春节期间每周两篇的更新。打磨一篇......
  • SAP动态安全库存(Dynamic Safety stock)配置及计算逻辑说明测试
    概念及计算逻辑:动态安全库存(DynamicSafetystock):它根据平均的日需求(Averagedailyrequirements)数量,来确定未来几个时期的安全库存水平(数量等于若干个平均日需求):最小库存、目标库存、最大库存。若小于最小库存,产生补货请求至目标库存;若大于最大库存,系统将提示例外信息。若同时设......
  • k8s DCGM GPU采集指标项说明
    dcgm-exporter采集指标项指标解释dcgm_fan_speed_percentGPU风扇转速占比(%)dcgm_sm_clockGPUsm时钟(MHz)dcgm_memory_clockGPU内存时钟(MHz)dcgm_gpu_tempGPU运行的温度(℃)dcgm_power_usageGPU的功率(w)dcgm_pcie_tx_throughputGPUPCIeTX传......
  • Theano 中文文档 0.9 - 2. 发行说明
    2.发行说明译者:Python文档协作翻译小组,原文:ReleaseNotes。本文以CCBY-NC-SA4.0协议发布,转载请保留作者署名和文章出处。Python文档协作翻译小组人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。Theano0.8.2(2016年4月21日)这是一个小版本的发布,只支持cudnn......
  • bytehound centos 7构建说明
    bytehound已经提供了相关的包,但是因为依赖的glib版本比较高,低版本的centos不能运行(比如centos7),所以自己构建了一个版本的准备使用centos-release-scl,当然还需要rust可以先安装好,同时还需要node(需要yarn)yum-yinstallcentos-release-sclyuminstall-ydev......
  • 关于AWS-ElastiCache的Reserved nodes预留节点支付类型-费用说明
    关于AWS-ElastiCache的Reservednodes的购买(类似于EC2的RI),可以节省成本引擎,可以选择Redis或者Memcached,期限一般大多都支持1年或者3年的对于Offeringtype-产品类型,这里分类比其他产品要复杂一点、,分为【标准预留节点产品】与【旧式预留节点产品】这个还与节点类型有关系......
  • 【Jetpack】ViewModel + LiveData + DataBinding 综合使用 ( 核心要点说明 | 组合方式
    文章目录一、ViewModel+LiveData+DataBinding核心要点1、ViewModel使用要点2、LiveData使用要点3、DataBinding使用要点二、ViewModel+LiveData+DataBinding代码示例1、ViewModel+LiveData代码2、build.gradle构建脚本-启用DataBinding3、DataBinding布局文......
  • Linux目录说明
    Linux目录结构Linux是一种以Unix为基础的操作系统,具有与Unix相似的目录结构。Linux的文件系统是一个树形结构,所有的文件和目录都位于根目录下。以下是Linux目录结构的常见组成部分:/bin:二进制文件存放目录,包含许多常用命令、工具和可执行文件。/boot:启动加载程序和......
  • Java多线程之---用 CountDownLatch 说明 AQS 的实现原理
    本文基于jdk1.8。CountDownLatch的使用前面的文章中说到了volatile以及用volatile来实现自旋锁,例如java.util.concurrent.atomic包下的工具类。但是volatile的使用场景毕竟有限,很多的情况下并不是适用,这个时候就需要synchronized或者各种锁实现了。今天就来说一下几......