前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >final关键字详解

final关键字详解

作者头像
naget
发布2019-07-03 11:32:58
4730
发布2019-07-03 11:32:58
举报
文章被收录于专栏:VegoutVegout

微信公众号:Vegout 如有问题或建议,请公众号留言

保证不变性

final修饰的类不能被继承,修饰的方法不能被重写,修饰的变量不能被二次赋值,总之,final就是最终的意思,保证了不变性。除了对不变性的保障,对有序性final也做出了他的贡献。

保证有序性

在线程安全中,有三大特性需要保障——原子性,可见性,有序性。而final对于有序性拥有特殊的语义。当一个类的一个变量声明为final类型,那么这个类初始化完成时,这个final变量必定完成了初始化。这个我们可以和普通变量做一个对比

代码语言:javascript
复制
public  class FinalTest {
    final int a;
    int b;
    static FinalTest instance;
    private FinalTest(){
        a=1;
        b=2;
    }
    public void FinalTest init(){
       instance = new FinalTest();
    }
    public static void print(){
        FinalTest finalTest = instance;
        if(finalTest!=null)
        System.out.println("a="+finalTest.a+" "+"b="+finalTest.b);
    }
}

假设我们有两个线程,线程A执行init()方法,线程B执行print()方法,按照正常情况,打印出来的a=1,b=2。但是也有可能打印出a=1,b=0,为什么呢?init()函数调用构造函数进行了对象的初始化,当构造函数返回的时候,a和b一定完成了赋值吗?这是不一定的,因为编译器和处理器对代码的执行存在重排序的可能,普通变量的赋值可能被重排序到构造函数返回之后进行,所以打印出的b有等于0的可能,而final修饰的变量可以确保不会被重排序到构造函数之外,因此,对与final修饰的变量,确保了初始化的安全性。如果final修饰的是引用类型,那么相当于这个引用(地址)不可变,但引用指向的对象是可变的。这种情况下,对与有序性有如下保证

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

也就是说我们在构造函数中对于final修饰的引用型成员变量指向的对象的赋值可以在构造函数返回之前完成,同样,对于普通引用变量,享受不到这个保证。例如上边的例子修改为

代码语言:javascript
复制
public  class FinalTest {
    final int[] a = new int[10];
    int b;
    static FinalTest instance;
    private FinalTest(){
        a[0] = 1;
        a[1] = 2;
        b=2;
    }
    public void FinalTest init(){
       instance = new FinalTest();
    }
    public static void print(){
        FinalTest finalTest = instance;
        if(finalTest!=null)
        System.out.println("a="+finalTest.a+" "+"b="+finalTest.b);
    }
}

那么构造函数中的a[0]和a[1]必定是完成了初始化的,而不会被重排序到构造函数返回之后进行,这个也是final来保证的。 final保证了这么多,其实底层采用的就是内存屏障,当编译器检测到final类型的变量时,初始化时它对应的操作就不会进行以上的重排序,并且在合适的位置插入内存屏障,告诉处理器也不要进行重排序。

保证有序性的前提条件

以上说的对与final类型成员变量的初始化的保证还有一个前提条件——被构造对象不能在构造函数中逸出。如果上边构造函数改为

代码语言:javascript
复制
    private FinalTest(){
        a=1;
        b=2;
        instance = this;
    }

this在构造函数还没有完成之前就对其他线程可见,这是一种危险的操作,使得对象不能完成安全的初始化,可能在构造函数没有返回之前,B线程就调用了print方法,并且通过逸出的instance访问没有进行初始化的成员变量,从而对与final的有序性语义也就难以进行保证了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vegout 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 保证不变性
  • 保证有序性
  • 保证有序性的前提条件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档