JVM的重排序

重排序一般是编译器或执行时环境为了优化程序性能而採取的对指令进行又一次排序执行的一种手段。重排序分为两类:编译期重排序和执行期重排序,分别相应编译时和执行时环境。

在并发程序中,程序猿会特别关注不同进程或线程之间的数据同步。特别是多个线程同一时候改动同一变量时,必须採取可靠的同步或其他措施保障数据被正确地改动。这里的一条重要原则是:不要如果指令运行的顺序,你无法预知不同线程之间的指令会以何种顺序运行。

可是在单线程程序中,通常我们easy如果指令是顺序运行的,否则能够想象程序会发生什么可怕的变化。理想的模型是:各种指令运行的顺序是唯一且有序的,这个顺序就是它们被编写在代码中的顺序。与处理器或其他因素无关,这样的模型被称作顺序一致性模型。也是基于冯·诺依曼体系的模型。

当然,这样的如果本身是合理的,在实践中也鲜有异常发生。但其实,没有哪个现代多处理器架构会採用这样的模型。由于它是在是太低效了。

而在编译优化和CPU流水线中,差点儿都涉及到指令重排序。

编译期重排序

编译期重排序的典型就是通过调整指令顺序。在不改变程序语义的前提下。尽可能降低寄存器的读取、存储次数。充分复用寄存器的存储值。

如果第一条指令计算一个值赋给变量A并存放在寄存器中,第二条指令与A无关但须要占用寄存器(如果它将占用A所在的那个寄存器),第三条指令使用A的值且与第二条指令无关。

那么如果依照顺序一致性模型,A在第一条指令运行过后被放入寄存器,在第二条指令运行时A不再存在。第三条指令运行时A又一次被读入寄存器。而这个过程中,A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置。这样第一条指令结束时A存在于寄存器中,接下来能够直接从寄存器中读取A的值,减少了反复读取的开销。

重排序对于流水线的意义

现代CPU差点儿都採用流水线机制加快指令的处理速度。一般来说,一条指令须要若干个CPU时钟周期处理。而通过流水线并行运行。能够在同等的时钟周期内运行若干条指令。详细做法简单地说就是把指令分为不同的运行周期,比如读取、寻址、解析、运行等步骤。并放在不同的元件中处理。同一时候在运行单元EU中,功能单元被分为不同的元件。比如加法元件、乘法元件、载入元件、存储元件等,能够进一步实现不同的计算并行运行。

流水线架构决定了指令应该被并行运行,而不是在顺序化模型中所觉得的那样。

重排序有利于充分使用流水线,进而达到超标量的效果。

确保顺序性

虽然指令在运行时并不一定依照我们所编写的顺序运行,但毋庸置疑的是。在单线程环境下。指令运行的终于效果应当与其在顺序运行下的效果一致,否则这样的优化便会失去意义。

通常不管是在编译期还是执行期进行的指令重排序,都会满足上面的原则。

Java存储模型中的重排序

在Java存储模型(Java Memory Model, JMM)中,重排序是十分重要的一节。特别是在并发编程中。JMM通过happens-before法则保证顺序运行语义,假设想要让运行操作B的线程观察到运行操作A的线程的结果。那么A和B就必须满足happens-before原则,否则。JVM能够对它们进行随意排序以提高程序性能。

volatilekeyword能够保证变量的可见性。由于对volatile的操作都在Main
Memory中,而Main Memory是被全部线程所共享的。这里的代价就是牺牲了性能。无法利用寄存器或Cache。由于它们都不是全局的,无法保证可见性。可能产生脏读。

volatile另一个作用就是局部阻止重排序的发生。对volatile变量的操作指令都不会被重排序,由于假设重排序,又可能产生可见性问题。

在保证可见性方面,锁(包含显式锁、对象锁)以及对原子变量的读写都能够确保变量的可见性。可是实现方式略有不同。比如同步锁保证得到锁时从内存里又一次读入数据刷新缓存,释放锁时将数据写回内存以保数据可见。而volatile变量干脆都是读写内存。

时间: 2024-08-11 09:03:49

JVM的重排序的相关文章

volatile 与 JVM 指令重排序

前言: 在做单例模式时 有博客在评论区 推荐使用 volatile 关键字 进行修饰 然后用了两天时间查资料看文档 发现涉及的面太广 虽然已经了解为什么要使用 volatile + synchronized 来写单例模式  但是限于目前的水平有限 无法很好的进行个人总结故 转发一篇 总结得很不错的博客.留备今后复习. 博文地址:<a>http://www.cnblogs.com/dolphin0520/p/3920373.html</a>

为了防止程序重排序,慎用volatile

之前在InfoQ看到一篇关于java重排序的一篇文章,觉得里面有些知识写得太绝对了,于是想通过实际程序来说明一下: 关于java重排序,这里就不做介绍了,我们知道JVM底层封装了与OS的交互,它内部有自己的一套类似于OS的内存模型,程序重排序的设计思路基本上是来源于OS.下面直接入正题吧! 我们知道JVM给每个线程分配了自己的内存空间,也就是说在变量存储方面,分为主内存和线程工作内存,也就是说,所有线程共享主内存,每个线程都有自己的工作内存.程序执行的时候是去工作内存里面取值还是去主内存里面取值

JVM并发机制的探讨——内存模型、内存可见性和指令重排序

[转]http://my.oschina.net/chihz/blog/58035 文章写的非常好,为作者点赞. JAVA内存模型 对于我们平时开发的业务应用来说,内存应该是访问速度最快的存储设备,对于频繁访问的数据,我们总是习惯把它们放到内存缓存中,有句话不是说么,缓存就像是清凉油,哪里有问题就抹一抹.但是CPU的运算速度比起内存的访问速度还要快几个量级,为了平衡这个差距,于是就专门为CPU引入了高速缓存,频繁使用的数据放到高速缓存当中,CPU在使用这些数据进行运算的时候就不必再去访问内存.但

JVM 重排序

在java代码到最终执行的指令序列的整个过程中,会出现重排序.也就是说最终执行的顺序并不是按照源代码执行的顺序来进行的. 其中1为编译器的优化重排序,2,3是处理器的重排序. 数据依赖 如果两个操作访问同一个变量的话,而且其中一个为写操作,那么这两个操作是存在数据数据依赖的. JMM规定,存在数据依赖的操作的执行顺序不能被重排的.这里所说的数据依赖是指的是单个处理器或者单个线程之间的两个操作,对于不同处理器或者不同线程之间的数据依赖不被编译器或者处理器考虑. as-if-serial语义 指的是

volotile关键字的内存可见性及重排序

在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序. 1. 内存可见性 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程只能访问自己的工作内存,不可以访问其它线程的工作内存.工作内存中保存了主内存中共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中,其JVM内存模型大致如下图. 而JAVA内存模型规定工作内存与主内存之间的交互协议,其中包括8种原子操作: 1)

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它.比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, * initialized to indicate thread 'not yet started' */   p

深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]

在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的. 显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的. 原子操作的描述是: 多个线程执行一个操作时,其

多线程之指令重排序

为什么会乱序 现在的CPU一般采用流水线来执行指令.一个指令的执行被分成:取指.译码.访存.执行.写回.等若干个阶段.然后,多条指令可以同时存在于流水线中,同时被执行. 指令流水线并不是串行的,并不会因为一个耗时很长的指令在"执行"阶段呆很长时间,而导致后续的指令都卡在"执行"之前的阶段上. 相反,流水线是并行的,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可.比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处

《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before,

第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略等.它们的安全性都来自于JMM.本章将介绍Java内存模型的底层需求以及所提供的保证. 16.1 什么是内存模型,为什么需要它 16.1.1 平台的内存模型 在共享内存的多处理体系架构中,每个处理器都拥有自己的缓存,并且定期地与住内存进行协调.在不同的处理器架构中提供了不同级别的缓存一致性.要想确保