深入理解JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况。
内存模型
首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再把需求明确一点,一个java线程对一个变量的更新怎么通知到另外一个线程呢?我们知道java当中的实例对象、数组元素都放在java堆中,java堆是线程共享的。(我们这里把java堆称为主内存),而每一个线程都是自己私有的内存空间(称为工作内存),如果线程1要向线程2通信,一定会经过类似的流程:
1、 线程1将自己工作内存中的X更新为1并刷新到主内存中;
2、 线程2从主内存读取变量X=1,更新到自己的工作内存中,从而线程2读取的X就是线程1更新后的值。
从上面的流程看出线程之间的通信都需要经过主内存,而主内存与工作内存的交互,则需要Java内存模型(JMM)来管理器。下图演示了JMM如何管理主内存和工作内存:
当线程1需要将一个更新后的变量值刷新到主内存中时,需要经过两个步骤:
1、 工作内存执行store操作;
2、 主内存执行write操作;
完成这两步即可将工作内存中的变量值刷新到主内存,即线程1工作内存和主内存的变量值保持一致;
当线程2需要从主内存中读取变量的最新值时,同样需要经过两个步骤:
1、主内存执行read操作,将变量值从主内存中读取出来;
2、工作内存执行load操作,将读取出来的变量值更新到本地内存的副本;
完成这两步,线程2的变量和主内存的变量值就保持一致了。
可见性
Java中有一个关键字volatile,它有什么用呢?这个答案其实就在上述java线程间通信机制中,我们想象一下,由于工作内存这个中间层的出现,线程1和线程2必然存在延迟的问题,例如线程1在工作内存中更新了变量,但还没刷新到主内存,而此时线程2获取到的变量值就是未更新的变量值,又或者线程1成功将变量更新到主内存,但线程2依然使用自己工作内存中的变量值,同样会出问题。不管出现哪种情况都可能导致线程间的通信不能达到预期的目的。例如以下例子:
stop
= true;
这个经典的例子表示线程2通过修改stop的值,控制线程1中断,但在真实环境中可能会出现意想不到的结果,线程2在执行之后,线程1并没有立刻中断甚至一直不会中断。出现这种现象的原因就是线程2对线程1的变量更新无法第一时间获取到。
但这一切等到Volatile出现后,再也不是问题,Volatile保证两件事:
1、 线程1工作内存中的变量更新会强制立即写入到主内存;
2、 线程2工作内存中的变量会强制立即失效,这使得线程2必须去主内存中获取最新的变量值。
所以这就理解了Volatile保证了变量的可见性,因为线程1对变量的修改能第一时间让线程2可见。
指令重排序
关于指令排序我们先看一段代码:
int a = 0; boolean flag = false;
//线程1
public void writer() {
a = 1;
flag = true;
}
//线程2
public void reader() {
if (flag) {
int i= a+1;
...... }
}
线程1依次执行a=1,flag=true;线程2判断到flag==true后,设置i=a+1,根据代码语义,我们可能会推断此时i的值等于2,因为线程2在判断flag==true时,线程1已经执行了a=1;所以i的值等于a+1=1+1=2;但真实情况却不一定如此,引起这个问题的原因是线程1内部的两条语句a=1;flag=true;可能被重新排序执行,如图:
这就是指令重排序的简单演示,两个赋值语句尽管他们的代码顺序是一前一后,但真正执行时却不一定按照代码顺序执行。你可能会说,有这个指令重排序那不是乱套了吗?我写的程序都不按我的代码流程走,这怎么玩?这个你可以放心,你的程序不会乱套,因为java和CPU、内存之间都有一套严格的指令重排序规则,哪些可以重排,哪些不能重排都有规矩的。下列流程演示了一个java程序从编译到执行会经历哪些重排序:
在这个流程中第一步属于编译器重排查,编译器重排序会按JMM的规范严格进行,换言之编译器重排序一般不会对程序的正确逻辑造成影响。第二、三步属于处理器重排序,处理器重排序JMM就不好管了,怎么办呢?它会要求java编译器在生成指令时加入内存屏障,内存屏障是什么?你可以理解为一个不透风的保护罩,把不能重排序的java指令保护起来,那么处理器在遇到内存屏障保护的指令时就不会对它进行重排序了。关于在哪些地方该加入内存屏障,内存屏障有哪些种类,各有什么作用,这些知识点这里就不再阐述了。可以参考JVM规范相关资料。
下面介绍一下在同一个线程中,不会被重排序的逻辑:
这三种情况中,任意改变一个代码的顺序,结果都会大不相同,对于这样的逻辑代码,是不会被重排序的。注意这是指单线程中不会被重排序,如果在多线程环境下,还是会产生逻辑问题,例如我们一开始举的例子。
结语
本文简单介绍了java在实现线程间通信时的简单原理,并介绍了volatile关键字的作用,最后介绍了java当中可能会出现指令重排序的情况。下一篇将介绍JVM中的参数设置对java程序的影响。
参考资料:
《实战Java虚拟机》 葛一鸣
《深入理解Java虚拟机(第2版)》 周志明
《深入理解Java内存模型》 程晓明
深入理解JVM(二)——内存模型、可见性、指令重排序的更多相关文章
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- 深入理解JVM一内存模型、可见性、指令重排序
一.内存模型 首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再把需求明确一点,一个java线程对一个变量的更新怎么通知到另外一个线程呢?我们知道java当中的实例对象.数组 ...
- 深入理解JVM - Java内存模型与线程 - 第十二章
Java内存模型 主内存与工作内存 Java内存模型主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量(Variable)与Java编程中 ...
- JVM学习(八)指令重排序
一.数据依赖性 在学习JVM的指令重排序之前,我们先了解一下什么是数据依赖性: 编译器和处理器在处理具体的指令时,可能会对操作进行重排序来提高执行性能[多条指令并行执行,所以提升性能的同时也可能会导致 ...
- 单例模式+volatile禁止指令重排序
单例模式: 单例,顾名思义就是只能有一个.不能再出现第二个.就如同地球上没有两片一模一样的树叶一样. 在这里就是说:一个类只能有一个实例,并且整个项目系统都能访问该实例. 单例模式共分为两大类: 懒汉 ...
- 轻松学JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
- JVM学习--(二)内存模型、可见性、指令重排序
我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存模型 首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再 ...
- JVM并发机制的探讨——内存模型、内存可见性和指令重排序
并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”. 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CP ...
- JVM内存模型、指令重排、内存屏障概念解析
在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...
随机推荐
- PCIe link up bug 分析
Xilinx两块开发版PCIe link up时间相差很大,Virtex-6开发版PCIe link up时间超过60ms,而Virtex-7 PCIe link up时间只有~25ms. 分析过 ...
- MSSQL死锁进程查看及关闭
select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tran ...
- 【aardio】是否取消三个按键的对话框
import win; var id = win.msgbox("三个按钮","标题",0x3/*_MB_YESNOCANCEL*/) 参考标准库函数: nam ...
- node.js 线程调试配置
{ // 使用 IntelliSense 了解相关属性. // 悬停以查看现有属性的描述. // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linki ...
- EasyPR源码剖析(3):车牌定位之颜色定位
一.简介 对车牌颜色进行识别,可能大部分人首先想到的是RGB模型, 但是此处RGB模型有一定的局限性,譬如蓝色,其值是255,还需要另外两个分量都为0,不然很有可能你得到的值是白色.黄色更麻烦,它是由 ...
- Centos启动流程及grub legacy
Linux系统的组成部分:内核+根文件系统 内核的功能:进程管理.内存管理.网络管理.文件系统.驱动程序.安全功能 系统在运行时要么就是在运行内核代码,要么就是在运行应用程序代码.如果一个程序大多数时 ...
- 密码与安全新技术专题之AI与密码
20189217 2018-2019-2 <密码与安全新技术专题>第五周作业 课程:<密码与安全新技术专题> 班级: 1892 姓名: 李熹桥 学号:20189214 上课教师 ...
- Python爬虫学习--用Python结合Selenium实现 明日之子节目直播时为自己喜欢的选手自动点赞拉票!!!
声明:本脚本纯属娱乐,请勿用来非法点赞拉票,任何使用不当造成的后果自行承担. 闲话: 明日之子第二季开始好久了,作者一直再追,特别喜欢里面那个酷酷的小哥-蔡泽明.前两天晋选9大厂牌,采取的是直播的形式 ...
- 前端页面播放 rtmp 流与 flv 格式视频文件
技术 :angular/cli , html5 , typescript , scss ,es 6 ... 项目类型:直播视频与视频回放 使用到 插件 : videojs + ckplayer 遇到的 ...
- 第四次Scrum编码冲刺!!!!
一.总体任务: 本次冲刺是完成对图书馆管理系统的最后三个功能的实现------管理员对用户授权.用户注销和用户查询 二.个人任务及完成情况: 本人本次的任务是实现对管理员对用户授权部分的界面与部 ...