Java 内存模型
简略介绍一下 Java 内存模型
Java 内存模型即 Java Memory Model,简称 JMM。JMM 定义了 Java 虚拟机 (JVM) 在计算机内存 (RAM) 中的工作形式。JVM 是整个计算机虚构模型,所以 JMM 是隶属于 JVM 的。
Java 内存模型是共享内存的并发模型,线程之间次要通过读 - 写共享变量(堆内存中的实例域,动态域和数组元素)来实现隐式通信。Java 内存模型(JMM)管制 Java 线程之间的通信,决定一个线程对共享变量的写入何时对另一个线程可见。
JVM 主内存与工作内存
Java 内存模型的次要指标是定义程序中各个变量的拜访规定,即在虚拟机中将变量(线程共享的变量)存储到内存和从内存中取出变量这样底层细节。
Java 内存模型中规定了所有的变量都存储在主内存中,每条线程还有本人的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能间接读写主内存中的变量。这里的工作内存是 JMM 的一个抽象概念,也叫本地内存,其存储了该线程以读 / 写共享变量的正本。
就像每个处理器内核领有公有的高速缓存,JMM 中每个线程领有公有的本地内存。不同线程之间无奈间接拜访对方工作内存中的变量,线程间的通信个别有两种形式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采纳的是共享内存形式,线程、主内存和工作内存的交互关系如下图所示:
线程 A 和线程 B 通信要通过两个步骤:
线程 A 把本地内存 A 中更新过的共享变量刷新到主内存中
线程 B 到主内存中去读取线程 A 之前已更新过的共享变量
这里所讲的主内存、工作内存与 Java 内存区域中的 Java 堆、栈、办法区等并不是同一个档次的内存划分,这两者基本上是没有关系的,如果两者肯定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存次要对应于 Java 堆中的对象实例数据局部,而工作内存则对应于虚拟机栈中的局部区域。
JMM 数据原子操作
read(读取):从主内存读取数据
load(载入):将主内存读取到的数据写入工作内存
use(应用):从工作内存读取数据来计算
assign(赋值):将计算好的值从新赋值到工作内存中
store(存储):将工作内存数据写入主内存
write(写入):将 store 过来的变量值赋值给主内存中的变量
lock(锁定):将主内存变量加锁,标识为线程独占状态
unlock(解锁):将主内存变量解锁,解锁后其余线程能够锁定该变量
public class VolatileVisibilityTest {
private static volatile boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {
@Override
public void run() {System.out.println("waiting data...");
while (!initFlag) { }
System.out.println("====================success");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {prepareDate();
}
}).start();}
public static void prepareDate() {System.out.println("preparing data...");
initFlag = true;
System.out.println("prepare end...");
}
}
计算机高速缓存和缓存一致性
计算机在高速的 CPU 和绝对低速的存储设备之间应用高速缓存,作为内存和处理器之间的缓冲。将运算须要应用到的数据复制到缓存中,让运算能疾速运行,当运算完结后再从缓存同步回内存之中。
在多处理器的零碎中(或者单处理器多核的零碎),每个处理器内核都有本人的高速缓存,它们有共享同一主内存(Main Memory)。当多个处理器的运算工作都波及同一块主内存区域时,将可能导致各自的缓存数据不统一。
为此,须要各个处理器拜访缓存时都遵循一些协定,在读写时要依据协定进行操作,来保护缓存的一致性。
MESI 缓存一致性协定:多个 CPU 从主存读取同一个数据到各自的通知缓存,当其中某个 CPU 批改了缓存里的数据,该数据会马上同步会主内存,其余 CPU 通过总线嗅探机制能够感知到数据的变动从而将本人缓存里的数据生效。
volatile 缓存可见性实现原理
底层实现次要通过汇编 lock 前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主存
1)会将以后处理器缓存行的数据立刻写回到零碎内存
2)这个写回内存的操作会引起在其余 CPU 里缓存了该内存地址的数据有效(MESI)
重排序和 happens-before 规定
在执行程序时为了进步性能,编译器和处理器经常会对指令做重排序。重排序分三种类型:
编译器优化的重排序。编译器在不扭转单线程程序语义的前提下,能够重新安排语句的执行程序。
指令级并行的重排序。古代处理器采纳了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器能够扭转语句对应机器指令的执行程序。
内存零碎的重排序。因为处理器应用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
从 java 源代码到最终理论执行的指令序列,会别离经验上面三种重排序:
JMM 属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供统一的内存可见性保障。java 编译器禁止处理器重排序是通过在生成指令序列的适当地位会插入内存屏障(重排序时不能把前面的指令重排序到内存屏障之前的地位)指令来实现的。
happens-before
从 JDK5 开始,java 内存模型提出了 happens-before 的概念,通过这个概念来论述操作之间的内存可见性。如果一个操作执行的后果须要对另一个操作可见,那么这两个操作之间必须存在 happens-before 关系。这里提到的两个操作既能够是在一个线程之内,也能够是在不同线程之间。这里的“可见性”是指当一条线程批改了这个变量的值,新值对于其余线程来说是能够立刻得悉的。
如果 A happens-before B,那么 Java 内存模型将向程序员保障—— A 操作的后果将对 B 可见,且 A 的执行程序排在 B 之前。
重要的 happens-before 规定如下:
程序程序规定:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规定:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile 变量规定:对一个 volatile 域的写,happens- before 于任意后续对这个 volatile 域的读。
传递性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。
下图是 happens-before 与 JMM 的关系
最初
在文章的最初作者为大家整顿了很多材料!包含一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!这些材料的内容都是面试时面试官必问的知识点,篇章包含了很多知识点,其中包含了有基础知识、Java 汇合、JVM、多线程并发、spring 原理、微服务、Netty 与 RPC、Kafka、日记、设计模式、Java 算法、数据库、Zookeeper、分布式缓存、数据结构等等。
欢送关注公众号:前程有光,支付!