• volatile与重排序


      使用关键字volatile可以禁止代码的重排序;

      在Java程序运行时,JIT(即使编译器)可以动态地改变程序代码运行地顺序;例如,有如下代码:

    A代码-重耗时
    B代码-轻耗时
    C代码-重耗时
    D代码-轻耗时
    

      

      在多线程环境下,JIT有可能进行代码重排序,重排序后地代码顺序有可能如下:

    B代码-轻耗时
    D代码-轻耗时
    A代码-重耗时
    C代码-重耗时
    

      这样做地主要原因是CPU流水线是同时执行这4个指令的,那么轻耗时的代码在很大程度上先执行完成,以让出CPU流水线给其他指令,所以代码重排序是为了追求更高的程序运行的效率;

      重排序发生在没有依赖关系时,例如,对于上面的A,B,C,D代码,B,C,D代码不依赖A代码的结果,C,D代码不依赖A,B代码的结果,D代码不依赖A,B,C代码的结果,这种情况下就会发生重排序,如果代码之间有依赖关系,则代码不会重排序;

      使用关键字volatile可以禁止代码重排序,例如,有如下代码:

    A变量的操作
    B变量的操作
    volatile Z变量的操作
    C变量的操作
    D变量的操作
    

      

      那么会有4种情况发生:

    1. A,B可以重排序
    2. C,D可以重排
    3. A,B不可以重排到Z的后面
    4. C,D不可以重排到Z的前面

      

      换言之,变量Z是一个屏障,Z变量之前或之后不可以跨越Z变量,这就是屏障的作用,关键字synchronized具有同样的特性;

       1.关键字synchronized之前的代码不可以重排到synchronized之后

       2.关键字synchronized之后的代码不可以重排到synchronized之前

      使用双重检查锁实现多线程环境下的延迟加载单例模式

    public class Singleton {
        private static volatile Singleton singleton;
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
    
            return singleton;
        }
    }
    

      

      使用volatile修饰变量singleton使该变量在多个线程间达到可见性,另外也禁止了singleton =  new Singleton()的代码重排序,singleton = new Singleton()代码在内部分为3部分:

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

      

      在一些JIT编译器上,这种指令重排是真实发生的;

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

      所有线程在执行Java程序时都必须要遵守intra-thread semantics;intra-thread semantics保证重排序不会改变单线程内的程序结果;换句话说,intra-thread semantics允许那些在单线程内,不会改变单线程程序执行结果的重排序;

          

      当线程A,线程B执行时,B线程访问instance所引用的对象,但这个对象没有被线程A初始化,线程B将看到一个还没有被初始化的对象;

      这里的A2和A3虽然重排序了,但Java内存模型的intra-thread semantics将确保A2一定会排在A4前面执行;因此,线程A的intra-thread semantics没有改变,但A2和A3的重排序,将会导致线程B判断instance实例不为空,线程B接下来将访问instance引用的对象(上图中线程B中的虚线),此时线程B访问到的是一个没有没有初始化的对象(没有进行赋值的对象),返回的是一个空的对象;

  • 相关阅读:
    ABP学习之路--切换mysql数据库
    关于网站发布后站点访问404的问题
    关于COOKIE在本地可以正常写入发布后不能写入浏览器的问题
    关于CSS层叠样式(颜色+边框+文本属性)
    HTML 自学笔记(HTML框架+表单设计)
    HTML 自学笔记(HTML表格相关标记)
    HTML 自学笔记(HTML的图像标记+超链接的使用)
    HTML 自学笔记(HTML的基本结构+文档设置标记)
    .net的页面在大并发下偶尔出现503错误
    WebService:The remote server returned an error: (400) Bad Request
  • 原文地址:https://www.cnblogs.com/coder-zyc/p/12650493.html
Copyright © 2020-2023  润新知