zoukankan      html  css  js  c++  java
  • java内存模型之-final域

    1.1final域的重排序规则

    对于final域,编译器和处理器要遵守两个重排序规则。
    1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用
    变量,这两个操作之间不能重排序。
    2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能
    重排序。

     1.1.1写final域重排序规则

      写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含 下面2个方面。
      1)JMM禁止编译器把final域的写重排序到构造函数之外。
      2)编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障 禁止处理器把final域的写重排序到构造函数之外。 

      写普通域的操作被编译器重排序到了构造函数之外,读线程B错误地读取了 普通变量i初始化之前的值。而写final域的操作,被写final域的重排序规则“限定”在了构造函数 之内,读线程B正确地读取了final变量初始化之后的值。
      写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被 正确初始化过了,而普通域不具有这个保障。以上图为例,在读线程B“看到”对象引用obj时, 很可能obj对象还没有构造完成(对普通域i的写操作被重排序到构造函数外,此时初始值1还 没有写入普通域i)。

     

      1.1.2读final域重排序规则  

      读final域的重排序规则是,在一个线程中,初次读对象引用与初次读该对象包含的final 域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final 域操作的前面插入一个LoadLoad屏障。

    1.2 final 域为引用类型

      对于引用类型,写final域的重 排序规则对编译器和处理器增加了如下约束:在构造函数内对一个final引用的对象的成员域

    的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。下面用样例程序解释一下:

    public class FinalReferenceExample {
        final int [] intArray;          //final 是引用
        static FinalReferenceExample obj;
        public FinalReferenceExample (){  // 构造函数
            intArray = new int[1];        //1
            intArray[0] =1;               //2
        }
        public static  void writeOwo(){   //写线程A执行
            obj = new FinalReferenceExample();           // 3
        }
        public static  void writeTwo(){   //写线程A执行
            obj.intArray[0] = 2;           // 3
        }
        public static void reader(){      //写线程B执行
            if(obj != null){              // 4
                int temp1 = obj.intArray[0];
            }
        }
    }
    

      1是对final域的写入,2是对这个final域引用的对象的成员域的写入,3是把被 构造的对象的引用赋值给某个引用变量。这里除了前面提到的1不能和3重排序外,2和3也不能重排序。

    1.3 final引用不能从构造函数内溢出

    public class FinalReferenceEsacapeExample {
        final int i;
        static FinalReferenceEsacapeExample obj;
        public  FinalReferenceEsacapeExample(){
            i = 1;                         //1
            obj = this;             //2
        }
        public static void writer(){
            new FinalReferenceEsacapeExample();
        }
        public  static void reader(){
            if(obj != null){
                int temp = obj.i;
            }
        }
    
    }

    假设一个线程A执行writer()方法,另一个线程B执行reader()方法。这里的操作2使得对象 还未完成构造前就为线程B可见。即使这里的操作2是构造函数的最后一步,且在程序中操作2 排在操作1后面,执行read()方法的线程仍然可能无法看到final域被初始化后的值,因为这里的操作1和操作2之间可能被重排序

    从图3-32可以看出:在构造函数返回前,被构造对象的引用不能为其他线程所见,因为此 时的final域可能还没有被初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化之后的值。

    1.4final语义在处理器中的实现  

      上面我们提到,写final域的重排序规则会要求编译器在final域的写之后,构造函数return 之前插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操作前面插入 一个LoadLoad屏障。由于X86处理器不会对写-写操作做重排序,所以在X86处理器中,写final域需要的 StoreStore障屏会被省略掉。同样,由于X86处理器不会对存在间接依赖关系的操作做重排序, 所以在X86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说,在X86处理器 中,final域的读/写不会插入任何内存屏障!

      JSR-133专家组增强了final的语义。通过为final域增加写和读重排序 规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在 构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程 都能看到这个final域在构造函数中被初始化之后的值。

  • 相关阅读:
    AOJ 718.计算GPA
    AOJ 11.Rails
    AOJ 592.神奇的叶子
    AOJ 10.目标柏林
    洛谷P1030求先序排列
    vijos1514天才的记忆
    洛谷2016战略游戏
    LOJ10155数字转换
    洛谷2014选课
    洛谷2015二叉苹果树
  • 原文地址:https://www.cnblogs.com/sharing-java/p/10891449.html
Copyright © 2011-2022 走看看