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线程初始化过了,这是一个正确的读取操作。