使用最终字段的成本

maa*_*nus 19 java performance multithreading memory-fences

我们知道最终制作字段通常是个好主意,因为我们获得了线程安全性和不变性,这使得代码更易于推理.我很好奇是否有相关的性能成本.

Java内存模型保证了这一点final Field Semantics:

在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值.

这意味着对于像这样的类

class X {
    X(int a) {
        this.a = a;
    }
    final int a;

    static X instance;
}   
Run Code Online (Sandbox Code Playgroud)

每当线程1创建这样的实例

X.instance = new X(43);
while (true) doSomethingEventuallyEvictingCache();
Run Code Online (Sandbox Code Playgroud)

和线程2看到它

 while (X.instance == null) {
      doSomethingEventuallyEvictingCache();
 }
 System.out.println(X.instance.a);
Run Code Online (Sandbox Code Playgroud)

它必须打印43.如果没有final修饰符,JIT或CPU可以重新排序存储(第一个存储X.instance然后设置a=43),线程2可以看到默认初始化值并打印0.

当JIT看到final它显然不会重新排序.但它也必须强制CPU遵守命令.是否存在相关的性能损失?

nos*_*sid 8

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

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

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

如果有最终成员变量,编译器会发出附加指令.最有可能的是,这些附加指令会导致性能下降.但目前尚不清楚,这种影响是否对任何应用都有重大影响.

  • 基准测试先生对决赛的冻结处罚有一篇非常好的论文:http://shipilev.net/blog/2014/all-fields-are-final/ (8认同)