查看原文https://mp.weixin.qq.com/s?__biz=MzI3MTA1ODkzNg==&mid=2247488552&idx=1&sn=8faa38027d589f4aa5d250331e00b515&chksm=eac6c6bfddb14fa99044ee02aa87f70ecbb1abaae4bc4698158b1c28a78bffcd8210b891df43&token=325622416&lang=zh_CN#rd
JMM模型是什么?
Java内存模型(Java Memory Model简称JMM)是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。需要JVM的实现都需要遵守这样的规范,有了JMM规范的保bujj,并发程序运行在不同的虚拟机上时,所得到的程序结果才是安全可靠可信赖、不同JVM运行结果一致。
百度百科:
https://baike.baidu.com/item/java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/3399303
个人理解:比如读大学的时候有来自全国各地(不同硬件厂商、操作系统),但是只要是属于这个大学的学生,由学校统一安排(屏蔽差异)好每个人的学号(线程id)、教室(运行)、住宿(存储空间)等。
JMM主要解决什么问题?
由于我们软件是运行于硬件上面的,但是各大厂的硬件有所区别,导致可能在三星主板上和微星上面可能有所不一样,还有在windows、linux等系统上的系统上面所支持内存访问也有所区别,导致可能出现不一样的结果。由于java是跨平台的,基于这些差异,保证了java程序在各种平台下对内存的访问都能保证一致的执行结果,所以引入了JMM来屏蔽掉各种硬件和操作系统的内存访问差异,实现让java程序在各种平台下都通达到一致的内存访问效果。
当然由于JMM也存在共享数据区域,所以就会存在数据的 原子性、可见性、有序性。JMM通过java提供的 volatile解决可见性、通过synchronized和lock来解决有序性,那么原子性通过锁的方向进行实现。
JMM三大特性:原子性、可见性、有序性
原子性:一个或多个操作,要么全部成功,要么全部失败(不会存在中间状态);
可见性:指的修改了某个共享变量的值,其他线程可以立即获取到最新值;
有序性:指程序按照顺序从上到下,从左至右执行;
什么是happens-before 原则?
Happens-before是一个概念,一种现象,或者仅仅是一组规则,它定义了编译器或CPU对指令进行重新排序的基础。Happens-before不是Java语言中的任何关键字或对象,它只是一种规则,以便在多线程环境中,周围指令的重新排序不会导致代码产生不正确的输出。
happens-before 规定如下:
-
程序顺序规则(Program Order Rule):一个线程内,代码的执行顺序是从上到下(写在前面的先执行);
-
volatile变量规则(Volatile Variable Rule):volatile变量的变化,后续对所读的对象都是可见的。(即一改变立即可见)
-
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。(先解再加锁)
-
线程启动规则(Thread Start Rule):Thread对象start()方法先于其他操作。(先启动再能做其它操作)
-
线程终止规则(Thread Termination Rule):线程中的所有操作都先于线程的终结。(终结是生命周期最后)
-
线程中断规则(Thread Interruption Rule):对线程interrupt()方法的先于检测(先被中断才能被检测出来被中断了)
-
对象终结规则(Finalizer Rule) :一个对象的初始化完成(构造函数结束)先发生它的finalize()方法的开始。(先创建才能finalize)
-
传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。(顺序执行)
在java中从源码到最终执行会经历(源代码->编译器优化重排序->指令级并行重排序->内存系统重排序->最终执行的批评令序列)。jdk5开始,java使用新的JSR-133内存模型,其中就包括Happens-before,从而来辅助JMM中有序性通过通过硬件层面的as-if-serial来标识不让执行结果改变,来保证结果的一致性。
注意:volatile是禁止重排优化的。
参考:
https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html
https://www.geeksforgeeks.org/happens-before-relationship-in- java/#:~:text=Happens-before%20is%20not%20any%20keyword%20or%20object%20in,result%20in%20a%20code%20that%20produces%20incorrect%20output.
https://dzone.com/articles/happens-before-in-java-or-how-to-write-thread-safe
主内存与工作内存有什么区别?
主内存:主要用于存储的是java实例的对象,所有线程创建的实例对象都存放在这里,当然包括实例对象、成员变量、方法中的本地变量(局部变量)、静态变量、常量、及共享的类信息,往往这里存在多个线程同时访问和操作导致的线程安全问题。
工作内存:主要指的是线程从主内存拷贝使用到的变量副本,仅线本线程使用,其他线程不可见。
个人理解:主内存类似于露天停车场,大家都可以用(共用),工作内存类似于自已私有停车场(专用),当然这个有可能是租的,就是公共里面一些又特别收费的,呵呵~。
JMM的规定、操作及规则
规定
-
所有的共享变量都存储在主内存中;
-
每条线程都有自已的工作内存(类似高速缓存);
-
线程的工作内存保存被该线程使用到的变量的主内存副本拷贝;
-
线程对变量的所有操作(curd)都必须在工作内存中进行;
-
不同线程无法直接访问对方工作内存中的变量,线程值传递必须由主内存完成;
操作
-
read 读取:作用于主内存,将共享变量从主内存传送到线程的工作内存中。
-
load 载入:作用于工作内存,把 read 读取的值放到工作内存中的副本变量中。
-
store 存储:作用于工作内存,把工作内存中的变量传送到主内存中。
-
write 写入:作用于主内存,把从工作内存中 store 传送过来的值写到主内存的变量中。
-
use 使用:作用于工作内存,把工作内存的值传递给执行引擎,当虚拟机遇到一个需要使用这个变量的指令时,就会执行这个动作。
-
assign 赋值:作用于工作内存,把执行引擎获取到的值赋值给工作内存中的变量,当虚拟机栈遇到给变量赋值的指令时,就执行此操作。
-
lock锁定:作用于主内存,把变量标记为线程独占状态。
-
unlock解锁:作用于主内存,它将释放独占状态。
规则
-
不允许一个线程无原因地把数据从工作央存同步到主内存中;
-
不允许一个线程丢弃最近assign的操作而不同步到主内存中;
-
不允许一个变量在主内存多过”诞生“,也不允许在工作内存直接使用未初始化(load或assign)的变量;
-
不允许一个变量在同一时刻有多个线程对其进行lock操作,仅允许一个线程Lock住并重复执行多次,多次lock需要相同次数的unlock操作,变量才会解锁。
-
一个变量被Lock,会将清空工作内存中的此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
-
不允许UnLock,一个没有lock的变量,也不允许unlock一个被其他线程Lock的对象;
-
一个变量被执行unlock操作之前必须先把该变量同步回主内存中(执行store、write操作)
JMM不同于jvm模型有什么区别?
对比名称 | JMM | jvm内存 |
解决问题 | 原子性、有序性、可见性展开 | 对象内存开辟及生命周期 |
划分区别 | 工作区和公共内存数据区域 | 方法区、堆、JVM栈、本地方法栈、程序计数器 |
最后
根据JMM来了解java其实对于内存的操作有一个严格的标准规则,就类似于我们汽车在路上要遵循交通规则一样来约束我们安全驾驶,特别是这happens-before的使用很层使JMM的有序性得到进一步的保障。当然本人只讲JMM其时还有内存屏障未写到,考虑到篇幅原因后续再完善,有想再深入同学可以先了解一下cpu多级缓存以及下面的一些参考文章。
参考文章:
https://www.cs.umd.edu/~pugh/java/memoryModel/
https://blog.csdn.net/LYQ20010417/article/details/124138635
https://segmentfault.com/a/1190000017395235
https://zhuanlan.zhihu.com/p/372288168
https://blog.csdn.net/GarfieldEr007/article/details/83315848
https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html