final的内存语义

final域的重排序规则
对于final域,编译器和处理器要遵守两个重排序规则

1> 在构造函数内对一个final域的写入,与随后把这个构造函数的引用赋值给一个引用变量,两个操作不能重排序

2> 初次读一个包含final域对象的引用,和随后初次读这个final域,这两个操作不能重排序


class FinalExample{
    int i;//普通变量
    final int j;//final变量
    static FinalExample obj;
    public FinalExample(){//构造函数
        i = 1;//写普通域
        j = 2;//写final域
    }
    public static void writer(){//线程A写执行
        obj = new FinalExample();
    }
    public static void read(){//线程B读执行
        FinalExample fe = obj;//读取包含final域对象的引用
        int a = fe.i;//读取普通变量
        int b = fe.j;//读取final变量
    }
}

           写final域的重排序规则
写final域的操作不能重排序到构造函数之外,包含两个方面

1> JMM禁止编译器将写final域的操作重排序到构造函数外

2> 编译器会在final域的写入之后,构造函数return前,插入一个StoreStore屏障,这个屏障禁止处理器把final域的写重排序到构造函数之外

writer方法的调用,首先会构造一个实例,在将这个实例赋给一个引用,假设线程B读没有重排序的话

线程A中发生 写普通域的操作重排序到构造函数外面,读线程读取构造函数的引用,并去读普通域的值,就会读取到普通域的初值,而final域由于它的重排序特性,对final域的写入并不会重排序到构造函数外,这样读线程读取构造函数的引用是,就能正确读取到final域初始化后的值。

结论就是: 在一个对象的引用对一个线程可见前,能保证final变量被正确初始化,而普通域不具有这个特性,因为普通域的写入可能会重排序到构造函数外.也就是在多线程环境下,拿到一个对象的引用后,可能会出现它的普通属性的变量还没有被正确初始化的情况.

       读final域的重排序规则
读取一个final域的引用和随后读取这个final域,不能重排序

在多线程环境下,线程A执行writer方法中,final的写重排序规则,保证final域被其他线程初始化时候一定是正确初始化的,线程B执行reader方法,如果读取final域的操作重排序到读取包含final域的对象的引用之前,final变量都还没有被初始化,这是一个错误的读取操作,显然,当final引用读取之后,如果这个引用不为空,能够保证final变量被初始化过,这个读取就没有问题

结论:多线程环境下,final域的读取操作会重排序读取在包含final域的引用之后,但是普通域的读取操作可能排在,引用的前面。

       final域为引用类型
当final域是引用类型时,写final域的重排序规则对编译器和处理器增加下面约束:在构造函数内对一个final引用的对象的成员域的写入,和随后把这个构造函数的引用赋给一个引用变量,这两者之间不能重排序


class FinalReferenceExample{
    final int[] intArray;//为引用类型的final
    static FinalReferenceExample obj;
    public FinalReferenceExample(){//构造函数
        intArray = new int[1];//1
        intArray[0] = 1;//2
    }
    public static void writerOne(){//写线程A执行
        obj = new FinalReferenceExample();//3
    }
    public static void writerTwo(){//写线程B执行
        obj.intArray[0]=2;//4
    }
    public static void reader(){//读线程C执行
        if(obj!=null){//5
            int temp = obj.intArray[0];//6
        }
}
现在假设一种可能,写线程A执行完毕,写线程B执行,读线程C执行
写线程A执行,根据前面final域的重排序规则,操作1对final域的写入和操作2对final域的写入,不会重排序到操作3对象的引用赋给一个引用变量后面,也就是读线程C至少可以看到 intArray[0]为1,

而线程B的写入和线程C存在数据竞争,读线程C可能看不到线程B对intArray的写入,如果想要看到,需要同步来保证内存可见性.

     final引用不能从构造函数内溢出
写final域的重排序规则保证,在引用变量为任意线程可见之前,final域已经被正确初始化了,并且还要保证:
在构造函数内部,不能让这个对象的引用对其他线程可见,也就是对象引用不能在构造函数内溢出
————————————————
版权声明:本文为CSDN博主「z1340954953」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ditto_zhou/article/details/78738197

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值