jvm的happens-before原则
提到并发,通常首先想到是锁,其实对共享资源的互斥操作是一方面,在Java中还有一方面是内存的可见性和顺序化,了解JMM的同学可能会更清楚些,内存可见性和顺序性同样非常重要,在这里简单提一下JMM模型,首先介绍一下SMP(对称多处理结构)如下图:
在计算机中缓存到处可见,我们知道cpu的运算速度非常快,而从内存、甚至磁盘的读取速度则相对慢了几个数量级,所以缓存起到的是一个缓冲的作用,提高cpu相对的运算效率。SMP中每个cpu都有自己的缓存并且对其他cpu不可见,同时多个cpu共同享有一个主内存,主内存还每个cpu的缓存通讯通过总线IO来实现,因此当cpu缓存中对于主存数据的副本改变时,要同步的通过IO总线来刷新主存的数据,保证其他cpu看见得数据是合法的。在JMM中每个线程都有自己的工作内存,对其他线程不可见,同时有一个主内存,共所有的线程共享,java中有个volatile关键字,是一种轻量级的同步,主要是用来实现内存的可见性。有volatile关键字修饰的变量,当在线程的工作内存发生变化的时候,会同时写回到主内存,其他线程读取的时候,也会强制从主内存重读,这就保证其他线程读到的数据是正确的。
下面看一张别人画的图:
上面就是提到的JMM模型,实际上每个线程都有自己的工作内存且只对自己可见,而这里的共享内存,一般指的也是java中的堆。
上面提到过,现代的处理器由于处理速度非常快,因此通常都会有一个写内存,先把值保存到自己的缓存中,找个合适的实际在刷新到共享内存,因此这里对内存的操作可能存在可见性的问题,举个例子:
Processor A | Processor B |
---|---|
a = 1; //A1 x = b; //A2 |
b = 2; //B1 y = a; //B2 |
初始状态:a = b = 0 处理器允许执行后得到结果:x = y = 0 |
假设我们的代码如下:
a = 1;
b = 2;
x = b;
y = a;
其中a和b全为共享变量,可以理解是成员变量。
由于是多线程并发指向,完全可能出现上面表中的操作顺序。理论上即使是多线程也会得到x = 2;y = 1的结果(这里并没有数据争用),但是有可能会发生下面的情况:
处理器A(也可理解为线程A)的操作顺序是A1,A2,A3,处理器B的操作顺序是B1,B2,B3。
1.处理器A先把a=1写到自己的缓冲区,注意此时共享内存的a仍为0,于此同时处理B把b=2写到自己的缓冲区,但此时共享内存的b还是0。
2.处理器A从共享内存读取b的值,并赋值给x,于此同时处理器B从共享内存读取a的值,赋值给y。此时x = y = 0;
3.处理器A和B分别把自己缓冲区的值刷新到共享内存。
从代码层面看处理器A质性的是A1->A2,但是从内存可见性看,执行完刷新共享内存a的写入才算完成。因此这里的实际质性顺序是A2->A1,因此这里的指令被重排序了。因为大多数啊处理器都应用到了写缓冲区,所以重排序的特性很常见。
JMM针对这种重排序的特性会生成内存屏障指令来阻止某种程度的重排序,从而保证内存的可见性,cpu为了提高执行速度,会对我们的代码(编译后生成的指令)进行重排序,因此代码的执行顺序并不重要,只要我们的最终结果正确就行,因此有时候为了程序的正确性,jvm不得不作出某些动作来保证结果的可见性,这其中包括下面几种:
屏障类型 | 指令示例 | 说明 |
LoadLoad Barriers | Load1; LoadLoad; Load2 | 确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。 |
StoreStore Barriers | Store1; StoreStore; Store2 | 确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。 |
LoadStore Barriers | Load1; LoadStore; Store2 | 确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。 |
StoreLoad Barriers | Store1; StoreLoad; Load2 | 确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。 |
以第一个load-load为例子,该指令确保load1的操作在load2及其之后的所有load操作被执行前,执行,且保证load的值对所有的处理器可见。
实际上java中的voilate原语就是阻止对指令的重排序,volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障。(也就是说对于volatile变量,如果有线程修改了它的值,该值会马上对其他线程可见,并且一个线程读取该值的时候,其他线程缓存中的值会被同步刷新到最新值)。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。因此volatile使用需要谨慎,用的不好会造成性能的浪费(频繁的通过总线刷新各个处理器的值,可能造成数据风暴)。
为了简化这种可见性,java中有个
程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
传递性:如果A happens- before C,那么A happens- before C。
Thread.start()的调用会happens-before于启动线程里面的动作。
Thread中的所有动作都
解释一下第一条,在单线程中,一个线程的操作对该操作后续的所有操作都可见(注意
上面只是列了几个规则,实际可能不止这些,如果不满足上面的规则,则需要考虑使用同步等方法,来强制满足。
另外上面的几个规则是基于java的内存模型给出的,在java语言层面也给出了很多
happens-before简化了并发编程的难度,了解它的含义多少对我们有些好处。附上一张java的内存模型图:
jvm的happens-before原则的更多相关文章
- JVM内存调优原则及几种JVM内存调优方法
转载,如需帮助,请联系wlgchun@163.com https://blog.csdn.net/LeegooWang/article/details/88696195 如何对JVM进行内存调优? ...
- 深入理解JVM(六) -- GC执行原则和方案
上篇文章中,我们了解了Java虚拟机垃圾回收的思路和策略,这篇文章我们将了解Java是如何实现高效的回收算法的. 我们需要了解,内存回收必须要保证“一致性”,意思就是在执行GC分析的时候,系统看起来要 ...
- JVM虚拟机瓜分内存原则
操作系统分配给每个进程的内存是有限制的,例如32位的Windows限制为2GB.虚拟机提供了参数来控制java堆和方法区(非堆)这两部分内存的最大值.则剩余的内存为2GB(操作系统限制)减去Xmx(最 ...
- JVM探索之内存管理(三)
上节我们介绍了JVM垃圾回收的原则,还有几个垃圾收集算法:标记-清除算法.复制算法.标记整理算法.分代收集算法:现在将要说HotSpt的垃圾收集器,这小节将只是理论. Java虚拟机规范对垃圾收集器的 ...
- java虚拟机--jvm client模式与server模式的区别
JVM Server模式与client模式启动,最主要的差别在于:-Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升.JVM如果不显式指定是-Server模式还是-clien ...
- JVM入门
面试过程中,问到JVM一脸懵逼,在github看了一些文章,感觉质量不错,整理了一下希望大家不吝赐教. 目前主流的jdk采用解释与编译混合执行的模式,其JIT技术采用分层编译,极大地提升了Java的执 ...
- JVM内存结构与垃圾回收总结
1.JVM内存模型 JVM只不过是运行在你系统上的另一个进程而已,这一切的魔法始于一个java命令.正如任何一个操作系统进程那样,JVM也需要内存来完成它的运行时操作.记住:JVM本身是硬件的一层软件 ...
- 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?
本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...
- SpringBoot Quickstart
SpringBoot Intro SpringBoot是顺应现在微服务(MicroServices)理念而产生的一个微框架(同类微框架可供选择的还有Dropwizard), 用来构建基于Spring框 ...
- 1002-谈谈ELK日志分析平台的性能优化理念
在生产环境中,我们为了更好的服务于业务,通常会通过优化的手段来实现服务对外的性能最大化,节省系统性能开支:关注我的朋友们都知道,前段时间一直在搞ELK,同时也记录在了个人的博客篇章中,从部署到各个服务 ...
随机推荐
- BZOJ4399 魔法少女LJJ【线段树合并】【并查集】
Description 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了 LJJ感叹道"这里真是个迷人的绿色世界,空气清新.淡雅 ...
- BZOJ3144 Hnoi2013 切糕 【网络流】*
BZOJ3144 Hnoi2013 切糕 Description Input 第一行是三个正整数P,Q,R,表示切糕的长P. 宽Q.高R.第二行有一个非负整数D,表示光滑性要求.接下来是R个P行Q列的 ...
- Http中Get/Post请求区别
Http中Get/Post请求区别 (1)get是从服务器上获取数据,post是向服务器传送数据. (1) 在客户端,Get方式在通过URL提交数据,数据在URL中可以看到:POST方式,数据放置 ...
- win32鼠标和键盘相关函数
键盘相关函数:http://msdn.microsoft.com/en-us/library/windows/desktop/ms645530%28v=vs.85%29.aspx 鼠标相关函数:htt ...
- 关于altera fpga的io时序优化问题
chip planner中一个io的结构如下图所示 其中左边是输出部分右边是输入部分,但是会注意到两个结构:1,寄存器,2,delay模块 以下是我的推测:这两个结构是为了做时序优化时用的,在alte ...
- (转)完美解决 Android WebView 文本框获取焦点后自动放大有关问题
完美解决 Android WebView 文本框获取焦点后自动放大问题 前几天在写一个项目时,要求在项目中嵌入一个WebView 本来很快就完成了,测试也没有问题.但发给新加坡时,他们测试都会出现文本 ...
- 关于WCF引用方式之WCF服务寄宿控制台
1.创建解决方案WCFService 依次添加四个项目,如上图,Client和Hosting为控制台应用程序,Service和Service.Interface均为类库. 2.引用关系 Service ...
- 树的遍历——pat1043
http://pat.zju.edu.cn/contests/pat-a-practise/1043 给予N个数字组成二叉搜索树,判断这个数列是否由先序遍历得出或是镜像先序遍历得出,若是则输出相应的后 ...
- 用 Linux blkid 命令查找块设备详情
今天我们将会向你展示如何使用 lsblk 和 blkid 工具来查找关于块设备的信息,我们使用的是一台安装了 CentOS 7.0 的机器. lsblk lsblk 是一个 Linux 工具,它会显示 ...
- Car-eye-http-flv-module 实现nginx-rtmp-mudule HTTP方式的FLV直播功能
nginx-rtmp-mudule RTMP 是一款优秀的Car-eye-http-flv-module 是在nginx-rtmp-mudule RTMP基础上修改的流媒体服务器,除了支持flash播 ...