final这个修饰可以加在类、方法、变量上
- 加在类上面是让类不可以被继承,而且里面的方法全部默认为final修饰
- 加在方法上是让该方法不可以被子类重写
- 加在变量上,表示该变量变为常量,而且必须进行初始化
但其实final也是可以解决一些并发重排序问题的。
final域的重排序规则
final域也有自己的重排序规则
- 在构造函数来对一个final域进行写入,与之后把这个构造对象的引用赋值给一个引用变量,这两个操作是不可以发生重排序的,即初始化不可以与引用赋值发生重排序,跟volatile是一样的
- 第一次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间是不能发生重排序的
这两个规则分别对应final的读写的重排序规则
写final域的重排序规则
写final域的重排序规则其实就是上面的,初始化不可以与引用赋值发生重排序,必须先初始化,然后再进行引用赋值,但如果对于普通变量来说,也就是普通域,很可能会发生这两个步骤的重排序
这个规则可以确保,在对象引用为任意线程可见之时,对应的final域已经被正确初始化了
写final域的重排序规则是使用内存屏障来实现的
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障,这个屏障禁止了处理器将final域的写命令重排序到return之后,也就是构造函数之外
读final域的重排序规则
读final域的重排序规则就是,初次读对象引用与初次读对象里面的final引用是不可以发生重排序的,必须先读对象引用然后再读final引用
这个规则可以确保,在读一个final域之前,一定会先读包含这个final域的对象的引用,这是因为final域是依赖于对象的
读final域的重排序规则也是使用内存屏障来实现的
编译器会在都final域操作的前面插入一个LoadLoad屏障,确保前面如果有初次读对象操作时,要先读对象,然后再读final域
final域的引用类型
如果final域的变量是一个引用类型,那么对于读的重排序规则是没有变的,但写的重排序规则会增加多一条
在构造函数内对一个final引用的对象的成员域写入时,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作是不能重排序的。
即构造函数在初始化final引用对象时,构造函数之外的其他地方可能正在修改这个final引用对象,那么这两个操作是不可以发生重排序的,也就是一定要先构造函数初始化完final引用对象后才可以允许其他地方进行修改
所以总的final域的引用类型的写规则如下
- 构造函数里面初始化final引用的对象时,不可以被重排序到构造函数外
- 构造函数里面初始化final引用的对象前,外部不可以对final引用的对象进行修改
为什么final引用不能从构造函数内溢出?即发生重排序
我个人觉得,可能是因为final的引用每次改变都会成为一个新的对象,所以必须要确保改变是要按照顺序的
如果从构造函数内溢出,很有可能这个final引用还没从构造函数里面初始化好,外面的其他线程就会将其修改,就会导致了顺序扰乱现象
final的底层实现
前面已经对此提到过
- 在读final域时,会插入load-load屏障
- 在写final域时,会插入store-store屏障