一、什么是JMM
一种抽象的规范。每个JVM 的实现都要遵守这样的规范,这样才能保证Java程序能够“一次编写,到处运行”。
内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出这样的底层细节。
二、JMM结构
1、CPU缓存模型
直接上图:
《深入理解高并发编程》 6.1.2 章节CPU多级缓存架构原理。
ps:挡住的字是,计算机中的主内存是所有cpu都可以访问的,并且主内存的容量比cpu缓存大。
图片来源:https://github.com/Snailclimb/JavaGuide/issues/1848
解释一下:
计算机在执行程序时,每条指令都是在CPU中执行的。而执行指令的过程中,势必涉及到数据的读取和写入。
程序运行过程中的临时数据是存放在主存(物理内存)当中的,这个时候,在读取写入数据时,CPU处理速度是远远大于内存的处理速度的!两个完全不一致。
CPU缓存模型的工作方式: 先复制一份数据到 CPU Cache 中,当 CPU 需要用到的时候就可以直接从 CPU Cache 中读取数据,当运算完成后,再将运算得到的数据写回 Main Memory 中。但是,依然会存在内存缓存数据不一致问题,这时候,通过制定缓存一致协议(比如 MESI 协议)可以解决此问题。
2、JMM模型
图片来源我的另一篇博客:多线程与高并发(2)——synchronized用法详解
解释一下:
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。
主内存:所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
本地内存:每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
比如X=0,线程1改成了X=1,那么它要同步给主内存,主内存再给到线程2,这就是线程之间的可见性。
而,关于JMM的三大特性:原子性、可见性、有序性,可以参考多线程与高并发(2)——synchronized用法详解。
三、乱序执行&&禁止乱序
以下内容均可参考:
多线程与高并发(2)——synchronized用法详解。
多线程与高并发(5)——volatile关键字详解
多线程与高并发(3)——synchronized原理
1、乱序执行
cpu为了提高执行效率,当a指令去做其他事时,让b指令来执行。
不得不提的是,指令重排序。变量赋值操作的顺序与程序代码中的执行顺序是不一致的。
而为了防止乱序执行,JVM级别怎么规范呢?
类似于CPU内存屏障那样,JVM也定义了内存屏障。
2、volatile禁止乱序实现细节
(1)字节码层面
ACC_VOLATILE标识符。
(2)JVM层面
写的时候,前面加SS指令,后面加SL指令;
读的时候,前面加LL指令,后面加LS指令。
(3)OS和硬件层面
lock指令。
3、synchronized禁止乱序实现细节
(1)字节码层面
ACC_SYNCHRONIZED标识符,monitorenter和monitorexit标识符。
(2)JVM层面
c/c++调用操作系统提供的同步机制。
(3)OS和硬件层面
lock指令。