之前始终把 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