20

我们知道将字段设置为 final 通常是一个好主意,因为我们获得了线程安全性和不变性,这使得代码更容易推理。我很好奇是否有相关的性能成本。

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 服从命令。是否存在相关的性能损失?

4

1 回答 1

8

是否存在相关的性能损失?

如果您查看 JIT 编译器的源代码,您会在src/share/vm/opto/parse1.cpp文件中找到以下关于最终成员变量的注释:

这个方法(它必须是Java规则的构造函数)写了一个final。在构造函数发布对新构造函数对象的引用之后,所有初始化的效果必须在任何代码之前提交到内存。与其等待发布,我们只需在此处阻止写入。我们不是只对那些需要完成的写入设置障碍,而是强制所有写入完成。

如果有最终成员变量,编译器会发出额外的指令。最有可能的是,这些附加指令会导致性能损失。但目前尚不清楚这种影响是否对任何应用程序都很重要。

于 2014-05-07T20:07:47.963 回答