美文网首页
聊一聊final关键字

聊一聊final关键字

作者: Java技术范 | 来源:发表于2018-08-12 17:54 被阅读0次

    01.相关概念

        final关键字主要用来修饰类、方法和字段;当修饰类的时候,表示该类是不可继承的;当修饰方法的时候,表示该方法不可重写;当修饰字段的时候,表示该字段内容不可更改。

        相信对于Java基础比较好的以上的几点,相信大家都很熟悉了;但是在JMM中,final修饰的字段是禁止了一些重排序的

    02.重排序规则

        所谓重排序,在JMM中,有3中重排序:编译级重排序规则、指令级重排序规则和内存重排序规则。JMM为了提高Java程序性能,允许一些重排序规则,但是一些重排序规则会改变程序的结果。这个时候,我们使用一些工具来禁止JMM的重排序规则,JMM将这些工具称之为内存屏障。

        内存屏障:简单的理解,就是一组CPU指令。那么JMM定义了哪些内存屏障呢?

        在上述内存屏障中,StoreLoad 屏障是一个万能屏障,几乎拥有前3种屏障的所有功能。下面我们将会分析一下final关键字的内存语义和禁止重排序规则。

    03.final域内存语义

        我们先来说明针对于final关键字的两条重排序规则:

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


        2.初次读一个包含final域的对象引用,与随后初次读这个final域,这两个操作之间不能重排序。

        我们来看一下下面一段代码

    04.final域写内存语义

        

    针对于上述代码,这里的执行过程可以是这样的

    对于线程A执行的方法,主要分为两步:

    1. 就是在堆内存区域开辟空间,进行构造函数对对象初始化、   

    2. 将引用赋值给obj变量

    针对于上述final域,final域的写,我们可以看到在构造函数结束之前插入了StoreStore内存屏障,JMM无法将final域的写重排序到构造函数之外。但是对于一般的变量,为了提高Java程序的性能,允许将普通变量的写重排序到构造函数之外。

    针对于final域的写,这里有两条规则:

        1.JMM禁止将final域的写入重排序到构造函数之外

        2.编译器将会在final域的写入之后,return语句返回之前插入StoreStore内存屏障StoreStore内存屏障。

        在线程B中,针对于读取对象的实例数据部分,final 域的读取是初始化的值,但是对于一般变量的读取,可能读取的是初始化的旧值。

    05.final域的读语义

        在上一节内容中,线程B的执行顺序是一种可能,这里我们将会看到线程B可能另一种执行的顺序,具体如下图所示:

      针对于线程B,程序员认为的操作是这样的:

        1.对去对象引用后赋值给tmp变量

        2.读取普通变量

        3.读取final变量

    但是考虑到重排序问题,普通变量的读操作有可能会重排序到方法外面,显然这样的操作是不正确的;但是对于final域的读,一定是在读取final域的实例对象引用后在进行读取的。那么JMM如何实现禁止对于final域读取的重排序呢?   

         编译器会在读final域的前面插入LoadLoad内存屏障。

    06.final域是引用类型

        上面分析了final域是基本类型,如果是引用类型呢,将会是什么情况呢?

    这里,我们只是做一个总结:

        在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作是不能重排序的。

        具体的情况和上述一样,留给读者去思考。

    07.为什么要禁止final域的重排序呢?

        禁止final域的重排序是基于这样的考虑:如果一个线程,在读取final域的时候首先读取的final域的值是0,然后再次读取的final域值是1,这样有悖于final修饰的字段是常量的规定,所以在后来的Java规范中,增加了对final域的语义。

    相关文章

      网友评论

          本文标题:聊一聊final关键字

          本文链接:https://www.haomeiwen.com/subject/utzsbftx.html