近日,有热心市民质疑“Java内存模型”:线程是否会将需要操作的数据全部加载到内存中
根据《我是傻子》可以看出,当事人丹丹(化名)目前情绪稳定,似乎已经意识到了问题所在
是的,聪明球找到了答案(答案稍后会来)
事件发生后,群内大佬高度重视,立即召开线上会议,成立Java内存模型专家组响应,要求组织迅速妥善处理,迅速查明根本原因问题,立即组织在线问答,并提供进一步指导 努力防止同样的问题再次发生,防止兄弟们得到心仪的offer
一想到很多朋友对Java内存模型还没有理解,吃不饱睡不着,看到黑丝也无动于衷。
然后
用了几天,多发几根毛,试着帮你理解一波~
关于Java内存模型,大家可以多说多说,但不要慌,我们先来梳理一下问题:
正如当事人所说:
线程操作数据时,会将数据从主存复制一份到自己的工作内存中,操作完成后再写回主存。如果数据太大,会不会也被复制到工作内存中呢?
要理解这个问题,首先要研究一下什么是Java内存模型
很多同学会把Java内存模型和JVM内存模型混淆,这是两个完全不同的东西
Java Model:全称Java Model,简称JMM,是一种虚拟机规范,下面会详细介绍;
JVM内存模型:Java的全称,简称JVM,也是一种虚拟机规范,本文不讲jvm;
如果你想开发一个可以运行Java程序的虚拟机,你必须遵循这两个规范(当然,要遵循的规范要多得多)。只有这样,Java程序才能在你的虚拟机上愉快地运行。,我们最常见的 vm 遵循这些规范;
Java内存模型的由来
很长的故事
我会保持简短
问题的根源
这涉及到CPU厂商和内存厂商的发展历史。. .
我们吃鸡路,cpu执行指令时,往往需要操作内存中的数据
为了便于理解,我举个栗子,取i = i + 1
cpu首先从内存中读取i的当前值,加上+1,然后将计算结果写回内存
一开始一切都很好,但是随着技术的发展,CPU执行效率远远超过内存读写效率,所以出现了一个现象
CPU执行+1操作的时间很短,假设只需要1ms,而从内存中读取i并写回内存需要很长时间,假设为10ms
cpu显然只需要1ms,被内存拖到11ms。怎么受得了?
于是,机智的cpu厂想到了一个办法
解决方案
这种方法在《深入理解Java虚拟机》一书中也有提及
简单来说就是在CPU和内存之间增加了一层缓存,也就是我们通常所说的L1、L2、L3缓存。这个缓存一般很小,但是很快,你懂我的意思
注意:知识点来了,一定要把cpu缓存和记忆棒的内存分开
这是记忆棒的内存(可以在系统属性中查看)
这是cpu缓存(任务管理器-性能栏可以查看)
所以现在的操作流程变成了:
cpu会提前将要使用的数据从主存复制到缓存中。cpu在进行计算操作时,会依次从L1、L2、L3缓存中查找,如果需要数据,直接操作,计算完后flush到主存;如果没有,就去主内存找
解决了CPU被内存低效拉取的问题
很长一段时间过去了。. .
CPU厂商推出多核处理器,导致另一个问题:线程安全
多核处理器的每个核心都有自己的缓存(每个CPU架构都不一样,要看CPU厂商是怎么做的。目前市面上的CPU一般都是L1、L2独立,L3共享)
从上面可以看出,我的cpu的L1缓存是384k。这个384k不是六核共享的,而是6*32*2,如下图
现在,架构变成了
(这个图是简化版,实际的架构图比这个复杂很多,那些细节我懒得画了)
那么,现在问题来了,如果不同内核上的线程同时操作相同的数据会发生什么?
让我们假设
核心 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才能编译一次,到处运行
回到最初的起点,你记忆中的绿脸~
现在回答
说到这里,说一下CPU的低级冷知识
指令重排
在并发编程中,除了Java内存模型带来的线程安全问题外,CPU和虚拟机本身也存在类似问题
指令重排在大多数场景下确实可以提高效率,但是有些操作强烈依赖于代码执行的顺序。这时,我们需要关闭指令重排。相信很多朋友已经猜到了。
那就对了
关于,想要完全了解,还得说很多,这里就不多说了,改天再单独写一篇。
一个例子来说明什么是指令重排,以及如何防止它:
这段伪代码摘自《深入理解Java虚拟机》:
其中描述的场景是开发中常见的配置读取过程,但是我们在处理配置文件时一般不会遇到并发,所以我们没有意识到这会是一个问题。
试想一下,如果在定义变量的时候不做任何修改加载配置文件时出错,由于指令重排序的优化,线程A中的最后一段代码“=true”可能会提前执行(虽然这里使用Java作为伪代码,但是排序优化是机器级别的优化操作,提前执行意味着提前执行了这条语句对应的汇编代码),这样线程B中使用配置信息的代码可能会出现错误,关键字可以避免这种情况。发生
好了,我完成了
文章来源:https://www.163.com/dy/article/GTG8DF0P0552BFL9.html
感谢您的来访,获取更多精彩文章请收藏本站。
