您当前的位置: 首页 >  Java

顧棟

暂无认证

  • 2浏览

    0关注

    227博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java final原理

顧棟 发布时间:2022-04-21 08:00:00 ,浏览量:2

Java final原理

文章目录
  • Java final原理
    • final域重排序规则
      • final域为基本类型
        • 写final域的重排序规则
        • 读final域的重排序规则
      • final域为引用类型
        • 对final修饰的对象的成员域写操作
        • 对final修饰的对象的成员域读操作
      • 关于final重排序的总结
    • final的实现原理
      • 为什么final引用不能从构造函数中“逸出”
      • 使用 final 的限制条件和局限性
    • final基础使用
      • 作用范围

final域重排序规则

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

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

示例代码

public class FinalDemo {
    private int i;                        //普通域
    private final int j;                  //final域
    private static FinalDemo finalDemo;

    public FinalDemo() {
        i = 1;                            // 1. 写普通域
        j = 2;                            // 2. 写final域
    }

    public static void writer() {
        finalDemo = new FinalDemo();
    }

    public static void reader() {
        FinalDemo demo = finalDemo;       // 3.读对象引用
        int i = demo.i;                   // 4.读普通域
        int j = demo.j;                   // 5.读final域
    }
}
final域为基本类型 写final域的重排序规则

写final域的重排序规则禁止对final域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:

  • JMM禁止编译器把final域的写重排序到构造函数之外;
  • 编译器会在final域写之后,构造函数执行结束之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。

writer()方法中只有一行代码finalDemo = new FinalDemo();,这行代码有两个步骤:

  1. 构造一个FinalDemo的对象
  2. 将对象的引用赋值给引用变量finalDemo

假设线程B的读对象引用和读对象的成员域之间没有重排序(这其实是读final域的重排序规则),由于i,j之间没有数据依赖性,普通域(普通变量)i可能会被重排序到构造函数之外那么下图就是是执行顺序中的一种可能情况。

#mermaid-svg-FaxWsRiTEy7FMf7j {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j .error-icon{fill:#552222;}#mermaid-svg-FaxWsRiTEy7FMf7j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FaxWsRiTEy7FMf7j .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-FaxWsRiTEy7FMf7j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FaxWsRiTEy7FMf7j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FaxWsRiTEy7FMf7j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FaxWsRiTEy7FMf7j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FaxWsRiTEy7FMf7j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FaxWsRiTEy7FMf7j .marker.cross{stroke:#333333;}#mermaid-svg-FaxWsRiTEy7FMf7j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FaxWsRiTEy7FMf7j .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FaxWsRiTEy7FMf7j text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-FaxWsRiTEy7FMf7j .actor-line{stroke:grey;}#mermaid-svg-FaxWsRiTEy7FMf7j .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j .sequenceNumber{fill:white;}#mermaid-svg-FaxWsRiTEy7FMf7j #sequencenumber{fill:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j .messageText{fill:#333;stroke:#333;}#mermaid-svg-FaxWsRiTEy7FMf7j .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FaxWsRiTEy7FMf7j .labelText,#mermaid-svg-FaxWsRiTEy7FMf7j .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-FaxWsRiTEy7FMf7j .loopText,#mermaid-svg-FaxWsRiTEy7FMf7j .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-FaxWsRiTEy7FMf7j .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-FaxWsRiTEy7FMf7j .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-FaxWsRiTEy7FMf7j .noteText,#mermaid-svg-FaxWsRiTEy7FMf7j .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-FaxWsRiTEy7FMf7j .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FaxWsRiTEy7FMf7j .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FaxWsRiTEy7FMf7j .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FaxWsRiTEy7FMf7j .actorPopupMenu{position:absolute;}#mermaid-svg-FaxWsRiTEy7FMf7j .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-FaxWsRiTEy7FMf7j .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FaxWsRiTEy7FMf7j .actor-man circle,#mermaid-svg-FaxWsRiTEy7FMf7j line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-FaxWsRiTEy7FMf7j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程A 线程B 构造函数开始执行 写final域:j=2 【StoreStore屏障】 构造函数开始结束 把构造对象的引用赋值给finalDemo 读对象的引用finalDemo 读对象的普通域i 读对象的final域j 写普通域:i=1 线程A 线程B

线程B会错误的读取i的值,为之前的值0,但是读取final值时为正确的值2。

因此,写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障。比如在上例,线程B有可能就是一个未正确初始化的对象finalDemo。

读final域的重排序规则

读final域重排序规则为:在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序(注意,这个规则仅仅是针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。实际上,读对象的引用和读该对象的final域存在间接依赖性,一般处理器不会重排序这两个操作。但是有一些处理器(如alpha处理器)会重排序,因此,这条规则就是针对这些处理器而设定的。

read()方法主要包含了三个操作:

  • 初次读引用变量finalDemo;
  • 初次读引用变量finalDemo的普通域i;
  • 初次读引用变量finalDemo的final与j;

假设线程A写过程没有重排序,那么线程A和线程B有一种的可能执行时序为下图:

#mermaid-svg-pQvtixQavCiBmA5o {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-pQvtixQavCiBmA5o .error-icon{fill:#552222;}#mermaid-svg-pQvtixQavCiBmA5o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pQvtixQavCiBmA5o .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-pQvtixQavCiBmA5o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pQvtixQavCiBmA5o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pQvtixQavCiBmA5o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pQvtixQavCiBmA5o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pQvtixQavCiBmA5o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pQvtixQavCiBmA5o .marker.cross{stroke:#333333;}#mermaid-svg-pQvtixQavCiBmA5o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pQvtixQavCiBmA5o .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pQvtixQavCiBmA5o text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-pQvtixQavCiBmA5o .actor-line{stroke:grey;}#mermaid-svg-pQvtixQavCiBmA5o .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-pQvtixQavCiBmA5o .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-pQvtixQavCiBmA5o #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-pQvtixQavCiBmA5o .sequenceNumber{fill:white;}#mermaid-svg-pQvtixQavCiBmA5o #sequencenumber{fill:#333;}#mermaid-svg-pQvtixQavCiBmA5o #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-pQvtixQavCiBmA5o .messageText{fill:#333;stroke:#333;}#mermaid-svg-pQvtixQavCiBmA5o .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pQvtixQavCiBmA5o .labelText,#mermaid-svg-pQvtixQavCiBmA5o .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-pQvtixQavCiBmA5o .loopText,#mermaid-svg-pQvtixQavCiBmA5o .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-pQvtixQavCiBmA5o .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pQvtixQavCiBmA5o .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pQvtixQavCiBmA5o .noteText,#mermaid-svg-pQvtixQavCiBmA5o .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-pQvtixQavCiBmA5o .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pQvtixQavCiBmA5o .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pQvtixQavCiBmA5o .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pQvtixQavCiBmA5o .actorPopupMenu{position:absolute;}#mermaid-svg-pQvtixQavCiBmA5o .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-pQvtixQavCiBmA5o .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pQvtixQavCiBmA5o .actor-man circle,#mermaid-svg-pQvtixQavCiBmA5o line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-pQvtixQavCiBmA5o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程A 线程B 构造函数开始执行 读对象的普通域i 写普通域:i=1 写final域:j=2 【StoreStore屏障】 构造函数开始结束 把构造对象的引用赋值给finalDemo 读对象引用finalDemo 【LoadLoad屏障】 读对象的final域j 线程A 线程B

读对象的普通域被重排序到了读对象引用finalDemo的前面就会出现线程B还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而final域的读操作就“限定”了在读final域变量前已经读到了该对象的引用,从而就可以避免这种情况。

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。

final域为引用类型

上面介绍了final域是基础类型的情况,下面介绍final域为引用类型的情况。

public class FinalReferenceDemo {
    final int[] intArrays;                             //final修饰的是引用类型
    private FinalReferenceDemo finalReferenceDemo;

    public FinalReferenceDemo() {                      //构造函数
        intArrays = new int[1];                        //1
        intArrays[0] = 1;                              //2
    }

    public void writerOne() {                          //线程A
        finalReferenceDemo = new FinalReferenceDemo(); //3
    }

    public void writerTwo() {                          //线程B
        intArrays[0] = 2;                              //4
    }

    public void reader() {                             //线程C
        if (finalReferenceDemo != null) {              //5
            int temp = finalReferenceDemo.arrays[0];   //6
        }
    }
}
对final修饰的对象的成员域写操作

对于引用类型,在基础类型的规则之上,final域写针对编译器和处理器重排序增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。

针对上面的示例程序,线程A执行wirterOne方法,执行完后线程B执行writerTwo方法,然后线程C执行reader方法。下图就以这种执行时序出现的一种情况来讨论

#mermaid-svg-H5b5ZJTfQMvQhleN {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN .error-icon{fill:#552222;}#mermaid-svg-H5b5ZJTfQMvQhleN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H5b5ZJTfQMvQhleN .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-H5b5ZJTfQMvQhleN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H5b5ZJTfQMvQhleN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H5b5ZJTfQMvQhleN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H5b5ZJTfQMvQhleN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H5b5ZJTfQMvQhleN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H5b5ZJTfQMvQhleN .marker.cross{stroke:#333333;}#mermaid-svg-H5b5ZJTfQMvQhleN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H5b5ZJTfQMvQhleN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-H5b5ZJTfQMvQhleN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-H5b5ZJTfQMvQhleN .actor-line{stroke:grey;}#mermaid-svg-H5b5ZJTfQMvQhleN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN .sequenceNumber{fill:white;}#mermaid-svg-H5b5ZJTfQMvQhleN #sequencenumber{fill:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN .messageText{fill:#333;stroke:#333;}#mermaid-svg-H5b5ZJTfQMvQhleN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-H5b5ZJTfQMvQhleN .labelText,#mermaid-svg-H5b5ZJTfQMvQhleN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-H5b5ZJTfQMvQhleN .loopText,#mermaid-svg-H5b5ZJTfQMvQhleN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-H5b5ZJTfQMvQhleN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-H5b5ZJTfQMvQhleN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-H5b5ZJTfQMvQhleN .noteText,#mermaid-svg-H5b5ZJTfQMvQhleN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-H5b5ZJTfQMvQhleN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-H5b5ZJTfQMvQhleN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-H5b5ZJTfQMvQhleN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-H5b5ZJTfQMvQhleN .actorPopupMenu{position:absolute;}#mermaid-svg-H5b5ZJTfQMvQhleN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-H5b5ZJTfQMvQhleN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-H5b5ZJTfQMvQhleN .actor-man circle,#mermaid-svg-H5b5ZJTfQMvQhleN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-H5b5ZJTfQMvQhleN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程A 线程B 线程C 构造函数开始执行 1.写final类型引用 2.写final引用的成员域 【StoreStore屏障】 构造函数开始结束 3.把构造对象的引用赋值给引用变量finalReferenceDemo 5.读对象的引用finalReferenceDemo 【LoadLoad屏障】 6.读final引用的成员域 4.写final引用的成员域 线程A 线程B 线程C

由于对final域的写禁止重排序到构造方法外,因此1和3不能被重排序。由于一个final域的引用对象的成员域写入不能与随后将这个被构造出来的对象赋给引用变量重排序,因此2和3不能重排序。

对final修饰的对象的成员域读操作

JMM至少可以保证线程A的对final引用的成员域的写操作是可见的,就是线程C在读final引用的成员域intArrays[0]的值至少为1。 由于线程B、C之间存在着竞争关系,线程B对final引用的成员域的写操作,对线程C来说是不确定的,线程C在读读final引用的成员域intArrays[0]时可能看到2也可能看不到2,如果需要保证线程C可以看到线程B的写入操作,线程B、C之间需要通过同步原语(锁或者volatile)来确保可见性。

关于final重排序的总结

按照final修饰的数据类型分类:

  • 基本数据类型:
    • 写final域:禁止final域写与构造方法重排序,即禁止final域写重排序到构造方法之外,从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。
    • 读final域:禁止初次读对象的引用与读该对象包含的final域的重排序。
  • 引用数据类型:
    • 补充约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量进行重排序
final的实现原理

在处理器中的具体实现,final主要是还是通过内存屏障来阻止编译器处理器对指令的重排序:

  • 在写final域会要求编译器在final域写之后,构造函数返回前插入一个StoreStore屏障。

  • 在读final域的重排序规则会要求编译器在读final域的操作前插入一个LoadLoad屏障。

但是有的处理器会根据本身的特性优化屏蔽,如果以X86处理为例,X86不会对写-写重排序,所以StoreStore屏障可以省略。由于不会对有间接依赖性的操作重排序,所以在X86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说,以X86为例的话,对final域的读/写的内存屏障都会被省略!所以具体是否插入还是得看是什么处理器。

为什么final引用不能从构造函数中“逸出”

以下代码会使得final引用从构造函数中“逸出”,所谓溢出就是,final引用对其他线程可见。

public class FinalReferenceEscapeDemo {
    private final int i;
    private FinalReferenceEscapeDemo referenceDemo;

    public FinalReferenceEscapeDemo() {
        i = 1;                           //1 写final域
        referenceDemo = this;            //2 this引用在此逸出
    }

    public void writer() {
        new FinalReferenceEscapeDemo();
    }

    public void reader() {
        if (referenceDemo != null) {    //3
            int temp = referenceDemo.i; //4
        }
    }
}

假设线程A执行writer(),线程B执行reader()。这里的操作2使得对象在还未全部完成构造之前,就对线程B可见了,及时referenceDemo = this;在代码顺序的最后一步,因为操作1和操作2可能会被指令重排,如下图所示。

#mermaid-svg-JXyQ8gq9gNJ6x2Fn {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .error-icon{fill:#552222;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .marker.cross{stroke:#333333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actor-line{stroke:grey;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .sequenceNumber{fill:white;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn #sequencenumber{fill:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .messageText{fill:#333;stroke:#333;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .labelText,#mermaid-svg-JXyQ8gq9gNJ6x2Fn .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .loopText,#mermaid-svg-JXyQ8gq9gNJ6x2Fn .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .noteText,#mermaid-svg-JXyQ8gq9gNJ6x2Fn .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actorPopupMenu{position:absolute;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn .actor-man circle,#mermaid-svg-JXyQ8gq9gNJ6x2Fn line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-JXyQ8gq9gNJ6x2Fn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程A 线程B 构造函数开始执行 referenceDemo = this 2.被构造对象再次逸出 if (referenceDemo != null) 3.读取到不为null的对象引用 int temp = referenceDemo.i 4.读到的是final域初始化之前的值 i=1 1.写final域 构造函数开始结束 线程A 线程B

可以看出,在构造函数返回之前,被构造对象的引用不能为其他线程所见,不然final域的正确初始化得不到保障。

使用 final 的限制条件和局限性
  • 当声明一个 final 成员时,必须在构造函数退出前设置它的值。

    public class MyClass {
      private final int myField = 1;
      public MyClass() {
        ...
      }
    }
    
    public class MyClass {
      private final int myField;
      public MyClass() {
        ...
        myField = 1;
        ...
      }
    }
      
    
  • 将指向对象的成员声明为 final 只能将该引用设为不可变的,而非所指的对象。

  • 如果一个对象将会在多个线程中访问并且你并没有将其成员声明为 final,则必须提供其他方式保证线程安全。" 其他方式 " 可以包括声明成员为 volatile,使用 synchronized 或者显式 Lock 控制所有该成员的访问。

final基础使用 作用范围
  • 修饰类

    代表该类不能被继承。其中的所有方法都是隐身的final的,所以可以省略方法上的final。

  • 修饰方法

    • private 方法是隐式的final,private修饰的方法无法被继承,也无法被子类访问。不能被重写覆盖
    • final方法是可以被重载的 ,子类可以重载父类的公共final方法
  • 修饰参数

    Java允许在参数列表中以声明的方式将参数指明为final,这意味这你无法在方法中更改参数引用所指向的对象。这个特性主要用来向匿名内部类传递数据。

  • 修饰变量 变为常量一旦被赋值,就不能更改。

    • 静态变量

      只占据一段不能改变的存储空间,它必须在定义的时候进行赋值,否则编译器将不予通过。

    • final空白

      在定义处不进行赋值,在构造器中进行赋值,保证了该值在被使用前赋值。

主要来自《Java并发编程的艺术》 方腾飞 魏鹏 程晓明版

关注
打赏
1663402667
查看更多评论
立即登录/注册

微信扫码登录

0.0421s