final域的重排序规则

返回主目录

final修饰变量

  final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

  当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化;

  如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。

  本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

  final修饰一个成员变量(属性),必须要显示初始化。

  这里有两种初始化方式一种是在变量声明的时候初始化第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

//

可以做个试验,两种方式都不使用eclipse 都报错!如何是第一种方式初始化的,类申明属性时就初始化了,对所有线程可见了。不存在重排序(个人理解)

//

  当函数的参数类型声明为final时,说明该参数是只读型的。

JMM 详细介绍参考下文。

 http://ifeve.com/java-memory-model/

 存在重排序问题只能是第二种初始化。

写final域的重排序可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。

实现:

1.JMM 禁止编译器把final域的写重排序到构造函数之外。

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

特殊案例(代码参考上面的连接里的代码):

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。在这个示例程序中,如果该引用不为null,那么引用对象的final域一定已经被A线程初始化过了。

现在我们假设写线程A没有发生任何重排序,同时程序在不遵守间接依赖的处理器上执行,下面是一种可能的执行时序:

在下图中,读对象的普通域的操作被处理器重排序到读对象引用之前。读普通域时,该域还没有被写线程A写入,这是一个错误的读取操作。而读final域的重排序规则会把读对象final域的操作“限定”在读对象引用之后,此时该final域已经被A线程初始化过了,这是一个正确的读取操作。

猜你喜欢

转载自www.cnblogs.com/tianzhiyun/p/9339686.html