关于Java内存模型,能扯好多好多、能聊好远吗?

近日,有热心市民质疑“Java内存模型”:线程是否会将需要操作的数据全部加载到内存中

图片[1]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

根据《我是傻子》可以看出,当事人丹丹(化名)目前情绪稳定,似乎已经意识到了问题所在

是的,聪明球找到了答案(答案稍后会来)

事件发生后,群内大佬高度重视,立即召开线上会议,成立Java内存模型专家组响应,要求组织迅速妥善处理,迅速查明根本原因问题,立即组织在线问答,并提供进一步指导 努力防止同样的问题再次发生,防止兄弟们得到心仪的offer

一想到很多朋友对Java内存模型还没有理解,吃不饱睡不着,看到黑丝也无动于衷。

然后

用了几天,多发几根毛,试着帮你理解一波~

关于Java内存模型,大家可以多说多说,但不要慌,我们先来梳理一下问题:

正如当事人所说:

线程操作数据时,会将数据从主存复制一份到自己的工作内存中,操作完成后再写回主存。如果数据太大,会不会也被复制到工作内存中呢?

要理解这个问题,首先要研究一下什么是Java内存模型

很多同学会把Java内存模型和JVM内存模型混淆,这是两个完全不同的东西

Java Model:全称Java Model,简称JMM,是一种虚拟机规范,下面会详细介绍;

JVM内存模型:Java的全称,简称JVM,也是一种虚拟机规范,本文不讲jvm;

如果你想开发一个可以运行Java程序的虚拟机,你必须遵循这两个规范(当然,要遵循的规范要多得多)。只有这样,Java程序才能在你的虚拟机上愉快地运行。,我们最常见的 vm 遵循这些规范;

Java内存模型的由来

图片[2]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

很长的故事

图片[3]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

我会保持简短

问题的根源

这涉及到CPU厂商和内存厂商的发展历史。. .

我们吃鸡路,cpu执行指令时,往往需要操作内存中的数据

为了便于理解,我举个栗子,取i = i + 1

cpu首先从内存中读取i的当前值,加上+1,然后将计算结果写回内存

图片[4]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

一开始一切都很好,但是随着技术的发展,CPU执行效率远远超过内存读写效率,所以出现了一个现象

CPU执行+1操作的时间很短,假设只需要1ms,而从内存中读取i并写回内存需要很长时间,假设为10ms

cpu显然只需要1ms,被内存拖到11ms。怎么受得了?

于是,机智的cpu厂想到了一个办法

解决方案

这种方法在《深入理解Java虚拟机》一书中也有提及

图片[5]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

简单来说就是在CPU和内存之间增加了一层缓存,也就是我们通常所说的L1、L2、L3缓存。这个缓存一般很小,但是很快,你懂我的意思

注意:知识点来了,一定要把cpu缓存和记忆棒的内存分开

这是记忆棒的内存(可以在系统属性中查看)

图片[6]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

这是cpu缓存(任务管理器-性能栏可以查看)

图片[7]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

所以现在的操作流程变成了:

cpu会提前将要使用的数据从主存复制到缓存中。cpu在进行计算操作时,会依次从L1、L2、L3缓存中查找,如果需要数据,直接操作,计算完后flush到主存;如果没有,就去主内存找

图片[8]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

解决了CPU被内存低效拉取的问题

很长一段时间过去了。. .

CPU厂商推出多核处理器,导致另一个问题:线程安全

多核处理器的每个核心都有自己的缓存(每个CPU架构都不一样,要看CPU厂商是怎么做的。目前市面上的CPU一般都是L1、L2独立,L3共享)

从上面可以看出,我的cpu的L1缓存是384k。这个384k不是六核共享的,而是6*32*2,如下图

图片[9]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

现在,架构变成了

图片[10]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

(这个图是简化版,实际的架构图比这个复杂很多,那些细节我懒得画了)

那么,现在问题来了,如果不同内核上的线程同时操作相同的数据会发生什么?

让我们假设

核心 a 有线程 t1,核心 b 有线程 t2

开始计算前,内存中i的值为0,两个线程对应的缓存中i的值也为0

在某个时刻,两个线程同时执行 i + 1

t1 执行 i = 1 后,它会写回内存。这时候内存中i的值已经从0变成了1。

在 t2 执行 i = 1 之后,还将 i = 1 写回内存,这会覆盖 t1 写回的新 i 值

本来i应该两次+1后等于2,但实际结果等于1,明白我的意思,并发编程中的数据异常问题大多来自这个

因此,在并发编程中,只要涉及到写操作,就应该保证同步,以获得可靠的最终数据

在这里,我们可以总结一下内存模型

什么是 Java 内存模型

从上面的架构图可以看出,线程需要

如上所述,Java 内存模型是一种协议;如果一个线程要对数据进行操作,需要先从主存中读取到工作内存中,然后在操作完成后将数据写回主存。这看起来很简单,但在这两者之间有许多潜在的技术细节。,例如:

什么时候读?

什么时候会写?

多线程一起读写时如何分配?

那么问题来了,一台服务器上的CPU和内存可能是由不同的厂商提供的。如果它们的底层实现细节不匹配加载配置文件时出错,程序怎么能正常运行呢?每次设计产品时,都与所有制造商开会是不可能的。因此,为了方便和统一,就有了Java内存模型,用于标准化不同硬件和操作系统的内存读写的底层实现。不同之处;

只有屏蔽了这些差异,Java才能编译一次,到处运行

回到最初的起点,你记忆中的绿脸~

现在回答

图片[11]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

说到这里,说一下CPU的低级冷知识

指令重排

在并发编程中,除了Java内存模型带来的线程安全问题外,CPU和虚拟机本身也存在类似问题

指令重排在大多数场景下确实可以提高效率,但是有些操作强烈依赖于代码执行的顺序。这时,我们需要关闭指令重排。相信很多朋友已经猜到了。

那就对了

关于,想要完全了解,还得说很多,这里就不多说了,改天再单独写一篇。

一个例子来说明什么是指令重排,以及如何防止它:

图片[12]-关于Java内存模型,能扯好多好多、能聊好远吗?-4747i站长资讯

这段伪代码摘自《深入理解Java虚拟机》:

其中描述的场景是开发中常见的配置读取过程,但是我们在处理配置文件时一般不会遇到并发,所以我们没有意识到这会是一个问题。

试想一下,如果在定义变量的时候不做任何修改加载配置文件时出错,由于指令重排序的优化,线程A中的最后一段代码“=true”可能会提前执行(虽然这里使用Java作为伪代码,但是排序优化是机器级别的优化操作,提前执行意味着提前执行了这条语句对应的汇编代码),这样线程B中使用配置信息的代码可能会出现错误,关键字可以避免这种情况。发生

好了,我完成了

文章来源:https://www.163.com/dy/article/GTG8DF0P0552BFL9.html

------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享