我们知道最终制作字段通常是一个好主意,因为我们获得了线程安全性和不变性,这使得代码更易于推理。我很好奇是否存在相关的性能成本。
Java内存模型保证了这个final Field Semantics
:
在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值。
这意味着对于像这样的类
class X {
X(int a) {
this.a = a;
}
final int a;
static X instance;
}
每当线程1创建一个像这样的实例
X.instance = new X(43);
while (true) doSomethingEventuallyEvictingCache();
和线程2看到它
while (X.instance == null) {
doSomethingEventuallyEvictingCache();
}
System.out.println(X.instance.a);
必须打印43.如果没有final
修饰符,JIT或CPU可以重新排序存储(第一个存储X.instance
然后设置a=43
),线程2可以看到默认值 - 初始化值,而不是打印0。
当JIT看到final
时,它显然会避免重新排序。但它也必须强制CPU遵守命令。是否存在相关的性能损失?
答案 0 :(得分:8)
是否存在相关的性能损失?
如果您查看JIT编译器的源代码,您将在文件src/share/vm/opto/parse1.cpp中找到关于最终成员变量的以下注释:
这个方法(必须是Java规则的构造函数)写了一个final。在构造函数发布对新构造函数对象的引用之后,必须在所有代码之前将所有初始化的效果提交到内存。我们不是在等待发布,而是在这里阻止写入。我们强制完成所有写操作,而不是仅对那些需要完成的写操作设置障碍。
如果有最终成员变量,编译器会发出附加指令。最有可能的是,这些附加指令会导致性能下降。但是,如果这种影响对任何应用都很重要,那么目前还不清楚。