本文主要详解java Final关键字,更多Java技术知识,请登陆疯狂软件教育官网。
常用用法
java中final关键字大家经常使用。final可以用于声明字段、方法和类。final声明字段时,若为基本类型,表示该变量值初始化后不再改变;若为引用类型,则表示引用不可变,但引用所指向的对象是可以改变的。final声明方法时表示方法不可覆写(常用来限制子类不可以改写父类中方法)。final声明类时,表示类不可继承,如String类就是final的,你不能继承它。
final字段的详细语义与普通字段稍有不同。尤其是,编译器有很大的自由,能将对final字段的读操作移到同步屏障之外,然后调用任意或未知的方法。同样,也允许编译器将final字段的值保存到寄存器,在非final字段需要重新加载的那些地方,final字段无需重新加载。另外,将对象声明为不可变的,则可实现并发访问,即final可提供一种非同步状态下 轻量级的 线程安全方法。
详细语义
final字段语义有以下几个目标:
1、final字段的值不会变化。编译器不应该因为获得了一个锁,读取了一个volatile变量或调用了一个未知方法,而重新加载一个final字段。
2、一个对象,仅包含final字段且在构建期间没有对其他线程可见,应当视作不可变的,即使这类对象的引用在线程间传递时存在数据争用。
3、将字段 f 设为final,在读取 f 时应当利用最小的编译器/架构代价。
4、该语义必须支持诸如反序列化等场景,在这种情况下,一个对象的final字段会在该对象构建完成后改变。解释第二条语义之前,先说一下什么叫对象逸出。当某个不该被发布的对象被发布时即为逸出。如下示例:
public class FinalReferenceEscapeExample {
final int i;
static FinalReferenceEscapeExample obj;
public FinalReferenceEscapeExample () {
i = 1; //1写final域
obj = this; //2 this引用在此“逸出”,对象尚未构造完成时外部便可访问,此时的final字段是不安全的。
}
public static void writer() {
new FinalReferenceEscapeExample ();
}
public static void reader {
if (obj != null) { //3
int temp = obj.i; //4
}
}
}
第二条中的构建期间没有对其它线程可见 一般即指正确的发布对象,也就是发布期间对象不可逸出。关于第3条,先说说对于final字段编译器和处理器应该遵守的重排序规则
a、读final字段的重排序规则:
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。
初次读对象引用与初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此编译器不会重排序这两个操作。大多数处理器也会遵守间接依赖,大多数处理器也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序(比如alpha处理器),这个规则就是专门用来针对这种处理器。
b、写final字段重排序规则:
JMM禁止编译器把final域的写重排序到构造函数之外。
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。
写final字段的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final字段已经被正确初始化过了,而普通字段不具有这个保障。
如果final字段是引用类型,则写final字段的重排序规则增加了以下约束:
在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
public class FinalReferenceExample {
final int[] intArray; //final是引用类型
static FinalReferenceExample obj;
public FinalReferenceExample () { //构造函数
intArray = new int[1]; //1
intArray[0] = 1; //2
}
public static void writerOne () { //写线程A执行
obj = new FinalReferenceExample (); //3
}
public static void writerTwo () { //写线程B执行
obj.intArray[0] = 2; //4
}
public static void reader () { //读线程C执行
if (obj != null) { //5
int temp1 = obj.intArray[0]; //6
}
}
}
对上面的示例程序,我们假设首先线程A执行writerOne()方法,执行完后线程B执行writerTwo()方法,执行完后线程C执行reader ()。JMM可以确保读线程C至少能看到写线程A在构造函数中对final引用对象的成员域的写入。即C至少能看到数组下标0的值为1。而写线程B对数组元素的写入,读线程C可能看的到,也可能看不到。JMM不保证线程B的写入对读线程C可见,因为写线程B和读线程C之间存在数据竞争,此时的执行结果不可预知。
上面我们提到,写final域的重排序规则会要求译编器在final域的写之后,构造函数return之前,插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操作前面插入一个LoadLoad屏障。由于x86处理器不会对写-写操作做重排序,所以在x86处理器中,写final域需要的StoreStore障屏会被省略掉。同样,由于x86处理器不会对存在间接依赖关系的操作做重排序,所以在x86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说在x86处理器中,final域的读/写不会插入任何内存屏障!再说说上面的第4条,在有些时候(如反序列化),系统需要在对象创建完之后修改对象的final字段值。final字段可以通过反射和其他依赖于实现的方式来修改。这种情况唯一存在合理语义的场景是,对象被创建,然后其final字段被更新。在该对象的final字段的所有更新完成之前,该对象不应该对其他线程可见,且final字段也不应该被读取。在设置final的构造器结束时,以及通过反射或其他机制一修改完final字段,final字段就被冻结了。
详解java Final关键字
原创
©著作权归作者所有:来自51CTO博客作者小咸鱼干的原创作品,请联系作者获取转载授权,否则将追究法律责任
上一篇: Java实例变量初化顺序

提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
【Java 基础】-- Java final 关键字详解
使用final关键字定义常量时,通常与static这样可以确保PI作为全局常量不可更改。
java jvm 开发语言 System Java -
Java中的final关键字【详解】
目录一、Java中的final关键字1、final关键字2、final关键字修饰类3、final关键字修饰方法4、final关键字修饰变量5、final不能修饰6、fin
java final 常量 final关键字 赋值 -
final关键字内存屏障 java final关键字含义
final关键字在java中被称为完结器,表示最终的意思,意味着被final修饰的成员不能被改变。final修饰变量时,意味着该变量是常量;final修饰方法时,意味着该方法不能被子类重写;
final关键字内存屏障 java final关键字 子类 句柄