关闭

JVM 编译之指令重排

标签: jvm多线程线程
901人阅读 评论(1) 收藏 举报
分类:

介绍 :

所谓的指令重排指的就是jvm在编译代码的时候 ,为了提高程序运行效率,在不影响单线程程序执行结果的前提下,对指令进行的排序,当然我们这里的是单线程,如果是在多线程中就会影响程序的结果了


可能你听了我的介绍 还是不明所以,到底什么是指令重排?,没关系,下面我们通过代码来理解到底什么是指令重排

    1--->  int a = 2  << 1;
    2--->  int b = 3 << 1;
    3--->  int result = a * b + 2333;

相信你看到这段简短的代码,也会知道这段代码的执行顺序,1 - > 2 -> 3,相信你会觉得这就是代码执行的顺序,但是在jvm中是不会这样执行的,这里我们可以用着三个变量的依赖关系来解释一下原因


这三个变量中 1 与 2没有依赖关系 3与 1和2 都有依赖关系,也就是说,没有依赖关系的两段代码即使我们将他们编译执行的顺序进行调换,这样也不会对代码的结果产生改变 也就是 上面的代码在jvm中实际上是 2 -> 1 -> 3

当然在单线程中JVM对代码进行指令重排并不会产生影响,但是在多线程中进行指令重排的话就会产生一些不确定的结果了,现在我们来看一下指令重排在多线程的一个经典的例子,单例模式,懒加载

public class DateBaseTools{
    private static DateBaseTools instance = null;

    public static DateBaseTools getSingInstance() {
        if (instance == null) {
            synchronized (DateBaseTools.class) {
                instance = new DateBaseTools();
            }
        }
        return  instance;
    }
    public void action() {
    System.out.println(">>>>>>>>>>");
    }
}

再上面的懒加载模式中,我们要是在多线程并发中调用这个单例的话,就会因为JVM的指令重排造成一些不可预料的结果,下面我们来分析一下
看似简单的一段赋值语句: instance = new DateBaseTools();,但是很不幸它并不是一个原子操作,其实际上可以抽象为下面几条JVM指令:

memory =allocate();    //1:分配对象的内存空间 
ctorInstance(memory);  //2:初始化对象 
instance =memory;     //3:设置instance指向刚分配的内存地址 

我们可以看出 第一条指令对第二条指令有依赖关系,但是第二条指令与第三条指令并没有依赖关系,所以根据JVM指令重排的规矩可以对第二条与第三条指令的执行顺序进行交换,这样看起来没有什么问题,但是在多线程中这样就会产生问题了


我们假定有两个线程,第一个线程调用单例模式的getSingInstance() 开始执行1 ->2 -> 3要是这个时候在JVM中执行的是 1 -> 3 ->2,也就是分配好内存空间后,为instance分配内存地址,这个时候线程二抢占cpu资源,执行getSingInstacne发现instance不为空 就会返回instance,这个时候返回的instanc还没进行初始化,肯定会报错了

解决方案

给单例类中引用的instance加上volatile关键字,volatile关键字有一个作用就是防止JVM对其进行指令重排序
在 volatile 变量的赋值操作后面会有一个内存屏障,大多数的处理器都支持内存屏障的指令。上面的代码在加上volatilc后getSingInstace操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。

1
0
查看评论

JVM之指令重排分析

引言:在Java中看似顺序的代码在JVM中,可能会出现编译器或者CPU对这些操作指令进行了重新排序;在特定情况下,指令重排将会给我们的程序带来不确定的结果....
  • blueheart20
  • blueheart20
  • 2016-08-08 15:49
  • 3761

奇怪的并发现象探究——JMM的指令重排、内存级指令重排

我们在平时所习惯的单线程编程中默认了一种乐观的模型——串行一致性。即在程序中只存在唯一的操作执行顺序,并且在每次读取变量时,都能获得在执行序列(任何处理器)最近一次写入该变量的值。但在JMM以及底层的任何一看现代多处理器架构中都不会提供这种串行一致性。这在并发编程中会造成一些在单线程环境下看来难以理...
  • yxc135
  • yxc135
  • 2013-12-31 19:53
  • 2190

java虚拟机的指令重排和CPU的指令重排

为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中的各个语句计算的先后顺序与输入代码中的顺序一致。与处理器的乱序执行优化类似,java虚拟机的即使编译器也有类似的指令重排...
  • xuqiaobo
  • xuqiaobo
  • 2016-09-14 09:33
  • 745

JVM 指令重排

1 概念 指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。 不同的指令间可能存在数据依赖。比如下面计算圆的面积的语句: doubl...
  • pxg943055021
  • pxg943055021
  • 2017-04-07 11:24
  • 267

Java并发:volatile内存可见性和指令重排

转载请注明出处:jiq•钦'stechnical Blogvolatile两大作用1、保证内存可见性2、防止指令重排 此外需注意volatile并不保证操作的原子性。内存可见性1 概念         ...
  • jiq408694711
  • jiq408694711
  • 2016-03-26 22:34
  • 4982

JVM 编译之指令重排

介绍 :所谓的指令重排指的就是jvm在编译代码的时候 ,为了提高程序运行效率,在不影响单线程程序执行结果的前提下,对指令进行的排序,当然我们这里的是单线程,如果是在多线程中就会影响程序的结果了可能你听了我的介绍 还是不明所以,到底什么是指令重排?,没关系,下面我们通过代码来理解到底什么是指令重排 ...
  • qq_33048603
  • qq_33048603
  • 2016-09-06 09:54
  • 901

重排数列

import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Scanner; public class Main {     /**     ...
  • bianenze
  • bianenze
  • 2017-09-18 21:42
  • 155

JVM指令重排导致Singleton双重锁定出错

指令重排导致单例模式失效 我们都知道一个经典的懒加载方式的单例模式: public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singl...
  • sdauzxl
  • sdauzxl
  • 2017-03-16 15:56
  • 549

并发程序的乱序之一:编译器指令重排

并发执行的程序存在三种场景的乱序,编译器对指令重排导致变量之间的隐式逻辑打乱,多线程的内核调度,多核之间的cache一致性。
  • MeRcy_PM
  • MeRcy_PM
  • 2016-01-11 14:13
  • 881

volatile对指令重排的影响

上一期介绍了volatile关键字对JVM主内存和工作内存的影响,没看过的小伙伴们可以点击下面链接:什么是 volatile 关键字?实在懒得去看也不要紧,我们简单回顾一下:volatile是一个轻量级的线程同步机制。它的特性之一,是保证了变量在线程之间的可见性。当一个线程修改了变量的值,新的值会立...
  • wufaliang003
  • wufaliang003
  • 5天前 08:13
  • 4
    个人资料
    • 访问:112192次
    • 积分:1387
    • 等级:
    • 排名:千里之外
    • 原创:41篇
    • 转载:3篇
    • 译文:0篇
    • 评论:32条
    博客专栏
    最新评论