当前位置:网站首页>并发编程 — 内存模型 JMM
并发编程 — 内存模型 JMM
2022-07-25 12:50:00 【搬运Gong】
首先,我们为什么要学习JMM ?JMM 内存模型又是个什么东西?这里不排除很多小伙伴面试的时候会被问到一些关系并发编程的知识点。
我们不能只会调用 API,更重要的是学习设计者的设计思路。这里整理了几道大厂的面试题,让我们从面试题入手,来彻底掌握Java 内存模型 JMM。
JMM 相关面试题
① 你知道什么是Java 内存模型 JMM 吗?
② JMM 与 volatile 它们两个之间的关系是什么?
③ JMM 有哪些特性?它的三大特性都是什么?
④ 为什么要有 JMM,它为什么出现?有什么作用和功能?
⑤ happens-before 先行发生原则了解过吗?
.....
JMM 是什么?
计算机硬件存储体系
学过计算机硬件的同学都知道,计算机配置的三大件:CPU、内存和磁盘。首先看一下电脑的 CPU 配置,下图是我个人电脑的配置:

另外看一下 Windows 系统的:

我们看到 CPU 中有一个 L1\L2\L3 的缓存,那么这三个缓存是干啥用的呢?这个要从计算硬件操作原理来分析,看下图:

从下至上,运行速度越来越快。举个栗子,比如主内存的运行速度是每秒计算 10 次,那么 CPU 寄存器的运行速度就是每秒计算 100 次,可以看出相差甚远,那么这个时候就会有一个问题,当寄存器计算完之后,将结果传输给内存的时候,发现,内存还没有计算完成,则需要等待内存计算完成后再进行传输,这样,就会导致 CPU 资源的浪费,没有合理的运用资源。所以CPU 和内存之间添加了多级的缓存,CPU 的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题。所以 CPU 将处理完的数据放到了高速缓存中,主内存通过高速缓存与 CPU 进行数据交互操作。又考虑到目前操作系统的不一致,比如有 Windows、Linux、MacOS,如何保证在各个操作系统上内存访问差异的问题呢?为了解决这个问题,在 Java 中给出了对应的解决方案,也就是 JMM内存模型(Java Memory Model)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一直的内存访问效果。

Java 中的定义
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
原则:
JMM的关键技术点都是围绕多线程的原子性、可见性和有序性展开的能干嘛?
1 通过JMM来实现线程和主内存之间的抽象关系。
2 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
JMM 三大特性
可见性
是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更。JMM 规定了所有的变量都存储在主内存中。
看一下下面这张图,CPU 寄存器是与高速缓存进行数据交互,主内存与高速缓存进行交互,CPU 并没有直接与主内存进行交互。

系统主内存共享变量数据修改被写入的时机是不确定的,多线程并发下很可能出现“脏读”,所以每个线程都有自己的工作内存(线程是私有的),线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝(从主内存中复制一份到自己的工作内存),线程对变量的所有操作(读取、赋值等)都必须在自己的工作内存中完成,而不能直接读写主内存的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要通过主内存来完成。


这就是所说的线程的可见性,当线程 A 对主内存中的变量进行更新时,需要及时通知其他的线程,到主内存中取最新的值,避免线程脏读的出现。
原子性
指一个操作是不可被打断的,即多线程环境下,操作不能被其他线程干扰。
我们通过见到的锁就是原子性的。
有序性
对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。但为了提升性能,编译器和处理器通常会对指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序化执行的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。
优缺点
JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。但是,指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致((即可能产生"脏读"),简单说,两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化。
从源码到最终执行的示例图:

单线程环境里面确保程序最终执行结果和代码执行的结果一致。
处理器在进行重排序时,必须要考虑指令之间的数据依赖性。
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
多线程对变量的读写过程

由于 JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(线程栈),工作内存是每个线程的私有数据区域,而 Java 内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有的线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
JMM 定义了线程和主内存之间的抽象关系
1 线程之间的共享变量存储在主内存中(从硬件的角度来说就是内存条)
2 每个线程都有一个私有的工作内存,本地工作内存中存储了该线程用来读写共享变量的副本(从硬件的角度来说就是 CPU 的缓存,比如寄存器、L1、L2、L3 缓存等)
多线程先行发生原则【happens-before】
在JMM中,如果一个操作执行的结果需要对另一个操作可见性或者代码重排序,那么这两个操作之间必须存在happens-before(先行发生)原则。逻辑上的先后关系。
举个栗子:
| x=5 | 线程 A执行 |
| y=x | 线程 B执行 |
| 上述称之为:写后读 |
问题是:
y 是否 等于 5?
如果线程 A的操作(x=5)happens-before(先行发生)线程 B 的操作(y=x),那么可以确定线程 B 执行后 y=5一定成立;
如果他们不存在 happens-before 原则,那么 y=5 不一定成立。
这就是 happens-before 原则的威力。(包含可见性和有序性的约束)
先行发生原则说明
如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成, 那么有很多操作都将会变得非常啰嗦,但是我们在编写Java并发代码的时候并没有察觉到这一点。我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这这是因为Java语言中JMM原则下有一个“先行发生”(Happens-Before)的原则限制和规矩,给你立好了夫规矩!
这个原则非常重要:
它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩又难懂的底层编译原理之中。
happens-before 总原则
1 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
happens-before 总结
在Java语言里面,Happens-Before的语义本质上是一种可见性
A Happens-Before B意味着A发生过的事情对B来说是可见的,无论A事件和B事件是否发生在同一个线程里。
JMM的设计分为两部分:
一部分是面向我们程序员提供的,也就是happens-before规则,它通俗易懂的向我们程序员阐述了一个强内存模型,我们只要理解 happens-before规则,就可以编写并发安全的程序了。另一部分是针对JVM实现的,为了尽可能少的对编译器和处理器做约束从而提高性能,JMM在不影响程序执行结果的前提下对其不做要求,即允许优化重排序。我们只需要关注前者就好了;也就是理解happens-before规则即可,其它繁杂的内容有JMM规范结合操作系统给我们搞定,我们只写好代码即可。
边栏推荐
- Word style and multi-level list setting skills (II)
- Force deduction 83 biweekly T4 6131. The shortest dice sequence impossible to get, 303 weeks T4 6127. The number of high-quality pairs
- pytorch创建自己的Dataset加载数据集
- Deployment of Apache website services and implementation of access control
- Shell常用脚本:获取网卡IP地址
- I want to ask whether DMS has the function of regularly backing up a database?
- [operation and maintenance, implementation of high-quality products] interview skills for technical positions with a monthly salary of 10k+
- Cyberspace Security penetration attack and defense 9 (PKI)
- 【问题解决】org.apache.ibatis.exceptions.PersistenceException: Error building SqlSession.1 字节的 UTF-8 序列的字
- B树和B+树
猜你喜欢

跌荡的人生

艰辛的旅程

【AI4Code】《CodeBERT: A Pre-Trained Model for Programming and Natural Languages》 EMNLP 2020

零基础学习CANoe Panel(14)——二极管( LED Control )和液晶屏(LCD Control)

B tree and b+ tree

Docker学习 - Redis集群-3主3从-扩容-缩容搭建

交换机链路聚合详解【华为eNSP】

A hard journey

Microsoft proposed CodeT: a new SOTA for code generation, with 20 points of performance improvement

cv2.resize函数报错:error: (-215:Assertion failed) func != 0 in function ‘cv::hal::resize‘
随机推荐
conda常用命令:安装,更新,创建,激活,关闭,查看,卸载,删除,清理,重命名,换源,问题
ECCV2022 | TransGrasp类级别抓取姿态迁移
零基础学习CANoe Panel(16)—— Clock Control/Panel Control/Start Stop Control/Tab Control
pytorch创建自己的Dataset加载数据集
Connotation and application of industrial Internet
Zero basic learning canoe panel (14) -- led control and LCD control
485通讯( 详解 )
Leetcode 0133. clone diagram
2022.07.24 (lc_6125_equal row and column pairs)
“蔚来杯“2022牛客暑期多校训练营2 补题题解(G、J、K、L)
【重温SSM框架系列】15 - SSM系列博文总结【SSM杀青篇】
Cyberspace Security penetration attack and defense 9 (PKI)
I want to ask whether DMS has the function of regularly backing up a database?
[problem solving] org.apache.ibatis.exceptions PersistenceException: Error building SqlSession. 1-byte word of UTF-8 sequence
Detailed explanation of flex box
ECCV 2022 | climb to the top semantickitti! Semantic segmentation of LIDAR point cloud based on two-dimensional prior assistance
【AI4Code】《Contrastive Code Representation Learning》 (EMNLP 2021)
Docker学习 - Redis集群-3主3从-扩容-缩容搭建
程序员奶爸自制AI喂奶检测仪,预判宝宝饿点,不让哭声影响老婆睡眠
[CSDN year-end summary] end and start, always on the way - "2021 summary of" 1+1= Wang "