JUC高级四:Java内存模型之JMM

JUC高级四:Java内存模型之JMM

1. 计算机硬件存储体系(JMM为什么诞生)

image-20230325110725072

image-20230325110952537

因为有这么多级的缓存(cpu和物理主内存的速度不一致的),CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题

在我们cpu寄存器和内存之间加上高速缓存来平衡寄存器的存储速度远大于内存的存储速度的问题

Java虚拟机规范中试图定义一种Java内存模型(java Memory Model,简称JMM) 来屏蔽掉各种硬件和操作系统的内存访问差异,
以实现让Java程序在各种平台下都能达到一致的内存访问效果。推导出我们需要知道JMM

2. Java内存模型Java Memory Model

JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性可见性有序性展开的。

2.1 能干嘛?

  1. 通过JMM来实现线程和主内存之间的抽象关系。
  2. 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。

3.JMM规范下三大特性

3.1 可见性

是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更 ,JMM规定了所有的变量都存储在主内存中。

image-20230325111642858

3.1.1 线程脏读问题

Java中普通的共享变量不保证可见性,因为数据修改被写入内存的时机是不确定的,多线程并发下很可能出现"脏读",所以每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等 )都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成

image-20230325111853041

个人总结:Java中普通的共享变量不保证可见性,假设在多线程情况下,每个线程启动时都会从主内存拷贝一份共享变量副本到自己线程私有的内存中,但由于变量数据在线程之间不可见,线程一修改了自己线程内的副本变量或者主内存中的变量,对线程二是不可见的,线程二任然使用的是自己线程的副本变量

我们想要线程一修改自己线程内的副本变量或者主内存中的变量时让线程二可见这就是可见性

3.2 原子性

指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰

3.3 有序性

3.3.1 指令重排

对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。但为了提升性能编译器和处理器通常会对指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序化执行的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序

指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致,即可能产生"脏读",简单说:两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化

从源码到最终执行示例图:

image-20230325112652552

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测,所以有时候我们会根据特殊场景业务禁止指令重排从而保证程序执行的有序性

4. JMM规范下,多线程对变量的读写过程

读取过程:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到的线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:

image-20230325114300061

总结:

  • 我们定义的所有共享变量都储存在物理主内存中
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
  • 线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)
  • 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行**(同级不能相互访问)**

5. JMM规范下,多线程先行发生原则之happens-before

在JMM中,如果一个操作执行的结果需要对另一个操作可见性或者代码重排序那么这两个操作之间必须存在happens-before关系。

5.0 先行发生原则作用

  • 如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将会变得非常啰嗦,但是我们在编写Java并发代码的时候并没有察觉到这一点。

  • 我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下有一个“先行发生”(Happens-Before)的原则限制和规矩

  • 它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。

5.1 示例1

image-20230325114954378

5.2. happens-before总原则(面试回答)

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。(这不就是依赖性吗?对某个操作有依赖,肯定先执行依赖啊)
  2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

5.3. happens-before之8条

5.3.1 次序规则

一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作;(前一个操作的结果可以被后续的操作获取。)

5.3.2 锁定规则

一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;(对于同一把锁objectLock,threadA一定先unlock同一把锁后B才能获得该锁, A 先行发生于B)

5.3.3 volatile变量规则

对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。

5.3.4 传递规则

如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;

5.3.5 线程启动规则(Thread Start Rule)

Thread对象的start()方法先行发生于此线程的每一个动作

5.3.6 线程中断规则(Thread Interruption Rule)

对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;

可以通过Thread.interrupted()检测到是否发生中断

5.3.7 线程终止规则(Thread Termination Rule)

线程中的所有操作都先行发生于对此线程的终止检测,

我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。

5.3.8 对象终结规则(Finalizer Rule)

一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

(对象没有完成初始化之前,是不能调用finalized()方法的)

5.4 示例2

image-20230325121900946

5.4.1 优化方式1:把getter/setter方法都定义为synchronized方法

虽然解决了线程安全的问题,但是读和写的并发量都下降了

image-20230325122047956

5.4.2 (推荐)优化方式2:把value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景

image-20230325122216897

5.5 happens-before总结

  • 在Java 语言里面,Happens-Before 的语义本质上是一种可见性
  • A Happens-Before B意味着A发生过的事情对B来说是可见的,无论A事件和B事件是否发生在同一个线程里.
  • JMM的设计分为两部分:
    一部分是面向我们程序员提供的,也就是happens-before规则,它通俗易懂的向我们程序员阐述了一个强内存模型,我们只要理解happens-before规则,就可以编写并发安全的程序了。
  • 另一部分是针对JVM实现的,为了尽可能少的对编译器和处理器做约束从而提高性能,JMM在不影响程序执行结果的前提下对其不做要求,即允许优化重排序。我们只需要关注前者就好了,也就是理解happens-before规则即可,其它繁杂的内容有JMM规范结合操作系统给我们搞定,我们只写好代码即可。

6. 面试题

  1. 你知道什么是Java内存模型JMM吗?
  2. JMM与volatile它们两个之间的关系?
  3. JMM有哪些特性or它的三大特性是什么?
  4. 为什么要有JMM,它为什么出现?作用和功能是什么?
  5. happens-before先行发生原则你有了解过吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/3087.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebRTC开源库内部调用abort函数引发程序发生闪退问题的排查

目录 1、初始问题描述 2、使用Process Explorer工具查看到处理音视频业务的rtcmpdll.dll模块没有加载起来 3、使用Dependency Walker工具查看到rtcmpdll.dll依赖的库有问题 4、更新库之后Debug程序启动时就发生异常,程序闪退 5、VS调试时看不到有效的函数调用堆…

十大Python可视化工具,太强了

今天介绍Python当中十大可视化工具,每一个都独具特色,惊艳一方。 Matplotlib Matplotlib 是 Python 的一个绘图库,可以绘制出高质量的折线图、散点图、柱状图、条形图等等。它也是许多其他可视化库的基础。 import matplotlib.pyplot as p…

OpenCV入门(二十)快速学会OpenCV 19 对象测量

OpenCV入门(二十)快速学会OpenCV 19 对象测量1.对象测量2.多边形拟合3.计算对象中心作者:Xiou 1.对象测量 opencv 中对象测量包括: 如面积,周长,质心,边界框等。 弧长与面积测量; …

《LKD3粗读笔记》(4)进程调度

1、多任务 什么是多任务操作系统? 能同时并发地交互执行多个进程。注意是并发而不是并行。特别地,在多处理机机器上可以实现真正意义上的并行,因为它长了多个脑子多任务操作系统有哪些分类? 非抢占式多任务(cooperati…

【云原生】Kubernetes(k8s)部署 MySQL+Dubbo+Nacos服务

一、说明二、部署 MySQL三、部署 Nacos四、部署 Dubbo 服务4.1. 创建镜像仓库的密钥4.2. 部署 provider 服务4.3. 部署 consumer 服务五、测试一、说明 本文介绍基于 Kubernetes(k8s) 环境集成阿里云 私有镜像仓库 来部署一套 Dubbo Nacos 的微服务系统,并使用 Ku…

7个最好的PDF编辑器,帮你像编辑Word一样编辑PDF

PDF 是具有数字思维的组织的重要交流工具。提供高效的工作流程和更好的安全性,可以创建重要文档并与客户、同事和员工共享。文档的布局已锁定,因此无论在什么设备上查看,格式都保持不变。这是让每个人保持一致的好方法——尤其是那些使用Micr…

C++中的引用

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C】 接下来就要开始进行C的学习路线了,听说这块的内容稍微难一些,不过我相信只要自己好好学习,态度…

java与Spring的循环依赖

java与Spring的循环依赖一、循环依赖是什么有什么危害二、循环依赖在Spring中的体现和类型三、Spirng如何解决循环依赖四、总结一、循环依赖是什么有什么危害 什么是循环依赖 java中循环依赖用一张图来说就是下图:在对象的创建过程中多个对象形成了依赖闭环&#xf…

初识linux之管道

一、进程间通信的概念大家都知道,进程是具有独立性的,因为一个程序运行起来生成进程时,也会生成它的进程结构体,即PCB,然后然后通过进程结构体中的结构体指针找到它的虚拟地址空间,然后再通过它的页表映射到…

C语言——字符函数和字符串函数【详解】(一)

文章目录函数介绍1.strlen2.strcpy3. strcat4. strcmp5. strncpy6. strncat7. strncmp8. strstr函数介绍 求字符串长度 strlen 长度不受限制的字符串函数(使用时不安全) strcpy strcat strcmp 长度受限制的字符串函数介绍(与长度不受限制函数…

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(9)

目录 写在前面: 题目:P1025 [NOIP2001 提高组] 数的划分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 输入格式: 输出格式: 输入样例: 输出样例: 解题思路: 代…

【数据结构】哈希表

目录 1、哈希表 1.1 哈希表的简介 1.2 降低哈希冲突率 1.3 解决哈希冲突 1.3.1 闭散列 1.3.2 开散列(哈希桶) 1、哈希表 1.1 哈希表的简介 假设我们目前有一组数据,我们要从这组数据中找到指定的 key 值,那么咱们目…

【Java集合面试宝典】HashMap的put流程和特性?HashMap的扩容机制?原理— day08

目录 数组和链表分别适用于什么场景,为什么? 数组 链表 List和Set的区别 List和Map、Set的区别 HashMap 、HashTable 和TreeMap有什么区别? hashmap的特性 HashMap和HashTable有什么区别?(必会) J…

【数据结构】树的介绍

文章目录前言树的概念及结构树的概念树的表示树在实际中的运用二叉树的概念及结构二叉树的概念现实中的二叉树特殊的二叉树二叉树的性质二叉树的储存结构顺序存储链式存储写在最后前言 🚩本章给大家介绍一下树。树的难度相对于前面的数据结构来说,又高了…

ESP32设备驱动-HDC1080温度湿度传感器驱动

HDC1080温度湿度传感器驱动 文章目录 HDC1080温度湿度传感器驱动1、HDC1080介绍2、硬件准备3、软件准备4、驱动实现1、HDC1080介绍 HDC1080 是一款集成温度传感器的数字湿度传感器,可在极低功耗下提供出色的测量精度。 HDC1080 在很宽的电源范围内工作,是一种低成本、低功耗…

“提效”|教你用ChatGPT玩数据

ChatGPT与数据分析(二) 上文给简单聊了一下为什么ChatGPT不能取代数据分析师,本文我们来深入感受一下如何让GPT帮助数据分析师“提效”。 场景一:SQL取数 背景:多数数据分析师都要用SQL语言从数据库中提取数据&#x…

ctfshow web入门 命令执行29-33

1.web29eval()函数是把所有字符串当作php代码去执行,这题过滤了flag,使用通配符绕过过滤应该要注意文件中没有重名的文件,或一部分是一样的文件payload:cecho%20nl flag.php; #官方解法,反引号表示执行系统命令,nl为linux系统命令…

springboot智慧外贸平台

053-springboot智慧外贸平台演示录像2022开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件&#xff…

干货:浅谈主数据管理项目建设思路

“主数据是数据之源,是数据资产管理的核心,是信息系统互联互通的基石,是信息化和数字化的重要基础。 ——《主数据管理实践白皮书》” 近期,国家印发《数字中国建设整体布局规划》,提出数字中国建设的整体框架…

I2C协议简介 Verilog实现

I2C协议 IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线&#xf…