java final总结

来源:互联网 发布:云转码软件 编辑:程序博客网 时间:2024/06/05 22:38

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

写final域的重排序规则
写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含下面2个方面:
JMM禁止编译器把final域的写重排序到构造函数之外。
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。
写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。

读final域的重排序规则
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。
初次读对象引用与初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此编译器不会重排序这两个操作。大多数处理器也会遵守间接依赖,大多数处理器也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序(比如alpha处理器),这个规则就是专门用来针对这种处理器。

为什么final引用不能从构造函数内“逸出”
写final域的重排序规则可以确保:在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。其实要得到这个效果,还需要一个保证:在构造函数内部,不能让这个被构造对象的引用为其他线程可见,也就是对象引用不能在构造函数中“逸出”。
在构造函数返回前,被构造对象的引用不能为其他线程可见,因为此时的final域可能还没有被初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化之后的值。

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


在JMM中要求final域(属性)的初始化动作必须在构造方法return之前完成。换言之,一个对象创建以及将其赋值给一个引用是两个动作,对象创建还需要经历分配空间和属性初始化的过程,普通的属性初始化允许发生在构造方法return之后(指令重排序)。
似乎这个问题变得很可怕,因为在Java程序中得到的对象竟然有可能还没有执行完构造方法内的属性赋值,但在大部分情况下,对象的使用都是在线程内部定义的,在单线程中是绝对可靠的,或者说在单线程中要求使用对象引用时,该对象已经被初始化好。但如果在此过程中有另一个线程通过这个未初始化好的对象引用读取相应的属性,那么就可能读取到的并不是真正想要的值。在Java中final可以保证这一点,所以它可以避免这种类型的逃逸问题。

但是它并不能完全解决所有的逃逸问题,而只是确保在构造方法return以前是会被初始化的,无法确保不与其他的指令进行重排序:

private static TestObject testObject = null;final int a;public 构造方法() {    a = 100;    testObject = this;          //这个地方可能和a=100发生指令重排序}public static void read() {    if(testObject != null) {    //对变量testObject.a做操作    }}

如果final所修饰的不是普通变量,而是数组、对象,那么它能保证自己本身的初始化在其外部对象的构造方法返回之前,但是它本身作为对象,对内部的属性是无法保证的。

构造方法还没做完,变量是什么样子呢?普通变量和final变量到底又有什么区别呢?

public class FinalConstructorTest {    static abstract class A {        public A() {            display();        }        public abstract void display();    }    static class B extends A {        private int INT = 100;        private final int FINAL_INT = 100;        private final Integer FINAL_INTEGER = 100;        private String STR1 = "abc";        private final String FINAL_STR1 = "abc";        private final String FINAL_STR2 = new String("abc");        private final List<String> FINAL_LIST = new ArrayList<String>();        public B() {            super();            System.out.println("abc");        }        public void display() {            System.out.println(INT);            System.out.println(FINAL_INT);            System.out.println(FINAL_INTEGER);            System.out.println(STR1);            System.out.println(FINAL_STR1);            System.out.println(FINAL_STR2);            System.out.println(FINAL_LIST);        }    }    public static void main(String []args) {        new B();    }}

参考:
http://ifeve.com/java-ace-5-2/
http://ifeve.com/java-memory-model/

0 0
原创粉丝点击