Java内存模型(和堆栈等不是同一层次的划分)
什么叫Java内存模型?
现代计算机通过指令的重排序来提升计算机的性能,而没有限制条件的指令重排序会使得程序的行为不可预测,JMM就是通过一系列的操作规则限制指令重排序的方式使得指令重排序不会破坏JMM提供的可见性,同时JMM通过让JVM在适当的位置插入内存栅栏来屏蔽JMM与底层平台内存模型之间的差异。
背景知识:
*每秒处理事务数:衡量一个服务性能的高低好坏,每秒处理事务数是重要的衡量指标之一
*高速Cache:由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机都不得不加入一层读写速度尽可能的接近处理器运算速度的高速缓存来作为内存和处理器直接的缓冲
*缓存一致性协议:用于处理给高速缓存中数据一致性的问题
*处理器<--->高速缓存<--->缓存一致性协议<--->主内存
*如果存在一个计算任务依赖另外一个计算任务中间结果,那么其顺序性并不能依靠代码的先后来保证,这是数据的依赖性,指令重排优化要遵循数据的依赖性
*Java线程<--->工作内存<--->sava和store操作<--->主内存
ps:放图的目的就是要类比上面两张图!!!
正文:
对比上面两张图,我们把处理器和java线程做类比,高速缓存和工作内存做类比,主内存是同一个,那么我们java中有没有类似缓存一致性协议的协议来处理工作内存和主内存之间的实现细节呢?即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回到主内存的呢
答案肯定是有的,java内存模型中(和java内存区域中的堆,栈,方法区等不是同一个层次的划分)定义了8种操作来实现主内存和工作内存之间的交互协议,每一种操作都是原子性的!
java内存模型中的8种操作:
1)lock:锁定,作用于主内存变量,把一个变量标识为一条线程独占状态
2)unlock:解锁,作用于主内存变量,把一个处于lock状态的变量释放出来,释放后的变量才可以被其他线程锁定
3)read:读取,作用于主内存的变量,把一个变量从主内存传输到线程工作内存,以便随后的load使用
4)load:载入,作用于工作内存变量,它把read操作从主内存中得到的值放入工作内存的变量副本中
5)use:使用,作用于工作内存变量,把工作内存变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令就会执行这个操作
6)assign:赋值,作用于工作内存变量,把从执行引擎接收到的值赋给工作内存变量,每当虚拟机遇到一个需要给变量赋值的字节码指令就会执行这个操作
7)store:存储,作用于工作内存变量,把一个工作内存变量的值送到主内存中,以便后面的write操作使用
8)write:写入,作用于主内存变量,把store操作从工作内存得到的变量的值放入主内存变量中
以上8个操作都是原子操作!
要从主内存读取一个数据,必定要执行read和load,而且read要在load前面执行,只是要求了执行的顺序,却没有要求一定要连续执行,所以我们可以进行指令重排,那我们这8种操作怎么保证多线程环境下是安全的呢?所以我们java中还存在8种操作的操作规则
操作规则:
1)read在load前,store在write前,都不能单独出现,得成对,出现还得满足顺序
2)工作内存中的变量改变后必须同步到主内存
3)不允许一个线程无原因的(没有发生任何assign操作)把数据从线程的工作内存同步到主内存中
4)新的变量只能在主内存中诞生,不允许在工作内存中使用一个没有被初始化(load和assign)的变量,即对一个变量实施use和store操作之前必须先执行过了assign和load
5)一个变量在同一时刻只允许一个线程对其进行lock,但lock操作可以被同一条线程执行多次,执行多次lock后只有进行相同次数的unlock才能释放变量
6)如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作来初始化变量的值
7)如果一个变量没有被lock操作锁定,那么不允许对它进行unlock操作,也不允许去unlock一个被其他线程lock的变量
8)对一个变量执行unlock操作之前,必须把此变量同步回到主内存中(执行store,write)
分析:
这8种操作加上这8种操作规则,虽然可以保证一些内存操作是安全的,但是它实现起来非常的繁琐,不好实现,我们有一种代替这8个规则的方法:先行发生原则,满足先行发生原则则可以保证这些内存在多线程环境下是安全的,如果不满足先行发生原则的话,我们的补救措施就是volatile和synchorized
先行发生原则(判断数据是否存在竞争,线程是否安全的重要依据):
天然的先行发生关系,这些先行发生关系无需同步就已经存在了,满足这些先行发生关系的话,我们就不可以对他们进行随意的指令重排,得设置内存屏障,防止被重排
具体的原则:
1)程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作,准确的说,应该是控制流顺序,而不是程序代码顺序,因为要考虑分支循环等结构
2)管程锁定原则:一个unlock操作先行发生于后面对同一个锁的lock操作,这里必须强调是同一个锁,后面是指时间上的先后顺序
3)volatile变量规则:对一个volatile变量的写操作要先行发生于后面对这个变量的读操作,这里的后面同样是指时间上的先后顺序
4)线程启动规则:Thread对象的start方法先行发生于此线程的每一个动作
5)线程终止原则:线程中的所有操作都优先发生于对此线程的终止检测,我们可以通过Thread.join方法,Thread.isAlive的返回值等手段检测到线程已经终止执行
6)线程中断规则:对线程interrupu方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted方法检测到是否有中断发生
7)对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize方法
8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作A,那么可以得出操作A先行发生于操作C的结论
分析:
两个操作如果不满足先行发生原则,那么这两个操作在并发环境下就是不安全的,需要采用volatile或者synchorized或者lock使得线程安全,如果他们满足先行发生原则,那么这两个操作在多线程环境下肯定是线程安全的
(当然,volatile在java里面的运算的非原子性的,导致volatile变量的运算在并发下也一样是不安全的,但是单个volatile变量的读写具有原子性!!!)
volatile变量规则:
关键字volatile是JVM中最轻量的同步机制,volatile具有两种特性:
*保证变量的可见性:对一个volatile变量的读,总是能看到(任意线程)对这个1volatile变量最后的写入,这个新值对于其他线程来说是立即可见的
*屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序优化进行的手段,下文有详细分析:
volatile语义并不能保证变量的原子性,对任意单个volatile变量的读写具有原子性,但类似于i++,i--这种复合操作不具有原子性,因为自增运算包括读取i,i+1,重新赋值三个步骤,并不具备原子性
由于volatile只能保证变量的可见性和屏蔽指令重新排序,只有满足下面两条规则时,才能使用volatile来保证并发安全,否则就需要加锁(synchorized,lock,Atomic原子类)来保证并发中的原子性
*运算结果不存在数据依赖,或者只有单一的线程改变变量的值
*变量不需要与其他状态变量共同参与不变约束
因为需要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下重新排序,volatile变量的写操作比读操作慢一些,但是其性能开销比锁低很多
Java内存模型(和堆栈等不是同一层次的划分)的更多相关文章
- 求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…
GitHub 4.1k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.1k Star 的 ...
- 再问你Java内存模型的时候别再给我讲堆栈方法区
在介绍Java内存模型之前,先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情.要说计算机的内存模型,就要说一下一段古老的历史,看一下为什么要有内存模型 ...
- java内存模型及分块
转自:http://www.cnblogs.com/BangQ/p/4045954.html 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏 1.JMM简介 i.内存模型概述 Ja ...
- java内存模型与线程(转) good
java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm http://developer.51cto.com/art/201309/410971_ ...
- JVM-7.Java内存模型与高效并发
更多内容参见<并发与同步>系列 一.引子 二.JMM 三.Java中的线程 四.线程安全 五.锁优化 一.引子 运算能力 摩尔定律:晶体管数量,代表的CPU的频率 Amdahl ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- Java 内存模型和硬件内存架构笔记
前言 可跟<主存存取和磁盘存取原理笔记>串着看 https://blog.csdn.net/suifeng3051/article/details/52611310 杂技 Java 内存模 ...
- Java内存模型与共享变量可见性
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:本文主要参考自<深入理解Java虚拟机(第二版)>和<深入理解Java内存模型> ...
- Java 内存模型 ,一篇就够了!
Java 虚拟机 我们都知道 Java 语言的可以跨平台的,这其中的核心是因为存在 Java 虚拟机这个玩意.虚拟机,顾名思义就是虚拟的机器,这不是真实存在的硬件,但是却可以和不同的底层平台进行交 ...
随机推荐
- easyUI按钮图表对照大全
easyUI图标与对照类的对应关系:
- Mysql 常用数据类型
double:浮点型,double(5,2) 表示最多5位,必须包含两位小数,最大值是 999.99 char:定长字符串类型,char(10) 表示必须放 10 的字节,没有就用空格补充 varch ...
- Android调用系统图库返回路径
调用系统图库: Intent intent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI); ...
- 短连接、长连接与keep-alive
短连接与长连接 通俗来讲,浏览器和服务器每进行一次通信,就建立一次连接,任务结束就中断连接,即短连接.相反地,假如通信结束(如完成了某个HTML文件的信息获取)后保持连接则为长连接.在HTTP/1.0 ...
- 算法:输入一个链表,输出该链表中倒数第k个结点。
算法:输入一个链表,输出该链表中倒数第k个结点.<剑指offer> 思路加到注释里面了: 1:两个if判断是否返回值为空,首个为空,没有第k个值: 2:for循环找到倒数第k个值,返回为a ...
- (网页)HTML5
1.html5基本格式: <!DOCTYPE> --> 文档类型声明. <html lang="zh-cn"> --> 表示html文档开始 & ...
- Git基本操作和GtHub 特殊技巧
<GitHub 入门与实践> 笔记 了解GitHub Git 和 GitHub 的区别 在Git中,开发者将源代码存入名为"Git仓库"的资料库中,并加以使用.而Git ...
- matlab练习程序(旋转矩阵、欧拉角、四元数互转)
欧拉角转旋转矩阵公式: 旋转矩阵转欧拉角公式: 旋转矩阵转四元数公式,其中1+r11+r22+r33>0: 四元数转旋转矩阵公式,q0^2+q1^2+q2^2+q3^2=1: 欧拉角转四元数公式 ...
- Android/IOS手机使用Fiddler抓包
对于Android和IOS开发及测试的同事来说抓包是一个很重要的事,有利于排查问题所在,快速定位问题.但长期以来一直没有一款可以快速抓包的工具,直到有了Fiddler2. 使用步骤: 1. Fidd ...
- [20190213]学习bbed-恢复删除的数据.txt
[20190213]学习bbed-恢复删除的数据.txt --//以前也做过类似测试,当时在用bbed做verify时错误都不处理,当时的想法就是能读出就ok了.--//而且当时也做成功,纯粹是依葫芦 ...