之前一直把 Java内存模型
和 JVM内存结构
搞混。 其实两者是不同的东西。
Java内存模型(Java Memory Model,JMM) 定义了 java 运行时如何与硬件内存进行交互,比如规定了一个线程如何看到其他内存修改后共享变量的值。一些高级特性也建立在JMM的基础上,比如volatile 关键字。
而 JVM 内存结构, 定义了 JVM 内存是如何划分的。
下面我们介绍JMM。并从三个方面介绍: 为什么,是什么,解决了什么
JMM
为什么要有Java内存模型
我们都知道一台基本的计算机有 CPU 和 内存 两个东西。
CPU负责计算, 内存负责存储运行的程序和数据。
每次CPU计算时它会通过地址总线(Address Bus)向内存发送一个地址信号,指定要读取内存单元的地址。 然后进行等待。
之后内存将数据通过数据总线返回给CPU, CPU会将数据加载到寄存器中处理。然后将结果存储回内存。
但随着计算机性能的发展,CPU的计算速度越来越快。这个等待的时间(即内存读写的速度)相比计算的时间变得越来越长,CPU就无法及时获得需要的数据,导致性能下降。
为了加快运行的速度,加了一个CPU 高速缓存。每次需要读取数据的时候,先从内存读取到CPU缓存中,CPU再从CPU缓存中读取。由于CPU高速缓存则直接集成在CPU内部,与CPU之间的距离更近,因此访问速度大大加快了。
实际上会分为 一级缓存、二级缓存、和三级缓存,并且会有多核CPU。如下图
在多核CPU的情况下,每个CPU都有自己的高速缓存,而它们又共享同一块主内存。 就会发生缓存一致性问题。
比如, 初始化 a = 0;
线程a执行 a = a + 1;
线程b执行 a = a + 1;
多核CPU下可能会发生下面这种情况:
- CPU1 执行线程a, 从内存读取 a 为0, 存到高速缓存中。
- CPU2 执行线程b, 从内存读取 a 为0, 存到高速缓存中。
- CPU1 进行运算,得出结果 1,写回内存 i 的值为 1。
- CPU2 进行运算,得出结果 1,写回内存 i 的值为 1。
而在我们看来,正确结果应该是2.
错误的原因就是: 两个CPU的高速缓存是相互独立的,无法感知相互数据变化。
这就造成了缓存一致性问题。
怎么在硬件层面上解决这个问题? 比如 MESI 协议
。
该协议定义了四种缓存数据状态:
- 修改(Modified):该数据在高速缓存中修改了,还没同步到主内存,数据只存在于本缓存,与主内存不一致
- 独占(Exclusive): 我刚从内存中读取出来,别人没读过,数据只存在于本缓存中,与主内存一致
- 共享(Shared): 很多人同时从内存读该数据,数据存在于很多缓存中,与主内存一致
- 失效(Invalid):有人对该数据操作了,这不是最新的,数据无效
在该协议下,上面执行的运算就变成了这样:
- CPU1 执行线程a, 从内存读取 a 为0, 存到高速缓存中。
- CPU2 执行线程b, 从内存读取 a 为0, 存到高速缓存中。
- CPU1 进行运算,得出结果 1,写回内存 i 的值为 1。通过消息的方式告诉其他持有 i 变量的 CPU 缓存,将这个缓存的状态值为 Invalid。
- CPU2 进行运算,从缓存中取值,但该值已经是Invalid,重新从内存中获取。读取 a为 1, 放入高速缓存
- CPU2 进行运算,得出结果 2,写回内存 i 的值为 2。
从上面的例子,我们可以知道 MESI 缓存一致性协议,本质上是定义了一些内存状态,然后通过消息的方式通知其他 CPU 高速缓存,从而解决了数据一致性的问题。
说了这么多, 到底为什么要有Java 内存模型呢?
原因之一就是上面提到的缓存一致性问题:在不同的 CPU 中,会使用不同的缓存一致性协议。例如 MESI 协议用于奔腾系列的 CPU 中,而 MOSEI 协议则用于 AMD 系列 CPU 中,Intel 的 core i7 处理器使用 MESIF 协议。
当然还有其他原因,比如指令重排序导致的可见性问题等
那么怎么统一呢? 要知道,Java 的最大特点是 Write Once, Run Anywhere
什么是JAVA内存模型
为了解决这个问题,Java封装了一套规范,这套规范就是Java内存模型。
Java内存模型 想 屏蔽各种硬件和操作系统的访问差异,保证了Java程序在各种平台下对内存的访问都能得到一致效果。目的是解决多线程存在的原子性、可见性(缓存一致性)以及有序性问题。
Java线程之间的通信就是由JMM 控制,从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。JMM的抽象示意图如图所示
实际上,这个本地内存并不真实存在,只是JMM的抽象概念,它既可能在缓存,也可能在寄存器等。
从图上可以看出,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。
JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。
可见性指: 指一个线程修改了共享变量的值后,其他线程是否能够立即看到这个修改的值。
JAVA内存模型解决了什么
JMM提供的一些高级特性解决了原子性,可见性的问题。
比如现在:
private volatile boolean isRunning = true;
public void run() {
while (isRunning) {
// do some work
}
}
public void stop() {
isRunning = false;
}
isRunning变量被声明为 volatile,这意味着所有的读写操作都是从主存中进行的,而不是从缓存中进行的。
又比如synchronized关键字不仅保证可见性,同时也保证了原子性(互斥性)。在更底层,JMM通过内存屏障来实现内存的可见性以及禁止重排序
比如输入
private static Object lock = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i
这里声明了一个名字为lock的对象锁。我们在ThreadA和ThreadB内需要同步的代码块里,都是用synchronized关键字加上了同一个对象锁lock
还没写完整,建议看https://zhuanlan.zhihu.com/p/29881777
总结
- 操作系统作为对底层硬件的抽象,自然也需要解决 CPU 高速缓存与内存之间的缓存一致性问题。各个操作系统都对 CPU 高速缓存与缓存的读写访问过程进行抽象,最终得到的一个东西就是「内存模型」。
- Java 语言作为运行在操作系统层面的高级语言,为了解决多平台运行的问题,在操作系统基础上进一步抽象,得到了 Java 语言层面上的内存模型。
- JMM是抽象的,他是用来描述一组规则,通过这个规则来控制各个变量的访问方式,围绕原子性、有序性、可见性等展开的。
参考:
http://concurrent.redspider.group/article/02/6.html
https://www.cnblogs.com/chanshuyi/p/deep-insight-of-java-memo…
https://zhuanlan.zhihu.com/p/29881777
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
相关推荐: R语言JAGS贝叶斯回归模型分析博士生延期毕业完成论文时间|附代码数据
原文链接:http://tecdat.cn/?p=23652 最近我们被客户要求撰写关于贝叶斯回归的研究报告,包括一些图形和统计输出。 本文为读者提供了如何进行贝叶斯回归的基本教程。包括完成导入数据文件、探索汇总统计和回归分析 在本文中,我们首先使用软件的默认…