当前位置:网站首页>并发编程 — 内存模型 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规范结合操作系统给我们搞定,我们只写好代码即可。
边栏推荐
- 卷积神经网络模型之——AlexNet网络结构与代码实现
- Business visualization - make your flowchart'run'(3. Branch selection & cross language distributed operation node)
- LeetCode 0133. 克隆图
- Shell common script: check whether a domain name and IP address are connected
- Migrate PaloAlto ha high availability firewall to panorama
- 485通讯( 详解 )
- 工业互联网的内涵及其应用
- Introduction to web security UDP testing and defense
- MySQL remote connection permission error 1045 problem
- 全球都热炸了,谷歌服务器已经崩掉了
猜你喜欢

LeetCode 0133. 克隆图
![[300 opencv routines] 239. accurate positioning of Harris corner detection (cornersubpix)](/img/a6/c45a504722f5fd6e3c9fb8e51c6bb5.png)
[300 opencv routines] 239. accurate positioning of Harris corner detection (cornersubpix)

Shell common script: check whether a domain name and IP address are connected

【AI4Code】《InferCode: Self-Supervised Learning of Code Representations by Predicting Subtrees》ICSE‘21

卷积神经网络模型之——GoogLeNet网络结构与代码实现

Eccv2022 | transclassp class level grab posture migration
![[rust] reference and borrowing, string slice type (& STR) - rust language foundation 12](/img/48/7a1777b735312f29d3a4016a14598c.png)
[rust] reference and borrowing, string slice type (& STR) - rust language foundation 12

B树和B+树

Substance Designer 2021软件安装包下载及安装教程

2022 年中回顾 | 大模型技术最新进展 澜舟科技
随机推荐
Zero basic learning canoe panel (16) -- clock control/panel control/start stop control/tab control
Shell常用脚本:获取网卡IP地址
Emqx cloud update: more parameters are added to log analysis, which makes monitoring, operation and maintenance easier
Docekr学习 - MySQL8主从复制搭建部署
Perf performance debugging
Chapter5 : Deep Learning and Computational Chemistry
交换机链路聚合详解【华为eNSP】
状态(State)模式
485 communication (detailed explanation)
go : gin 自定义日志输出格式
Selenium use -- installation and testing
Mlx90640 infrared thermal imager temperature sensor module development notes (V)
Software testing interview question: Please list the testing methods of several items?
2022.07.24 (lc_6124_the first letter that appears twice)
AtCoder Beginner Contest 261E // 按位思考 + dp
Oran special series-21: major players (equipment manufacturers) and their respective attitudes and areas of expertise
Make a general cascade dictionary selection control based on jeecg -dictcascadeuniversal
Deep learning MEMC framing paper list
massCode 一款优秀的开源代码片段管理器
Shell common script: check whether a domain name and IP address are connected