之前始终把 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 < 100; i++) {System.out.println("Thread A" + i);
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {synchronized (lock) {for (int i = 0; i < 100; i++) {System.out.println("Thread B" + i);
}
}
}
}
public static void main(String[] args) throws InterruptedException {new Thread(new ThreadA()).start();
Thread.sleep(10);
new Thread(new ThreadB()).start();}
这里申明了一个名字为 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