【问题标题】:volatile vs final fields performance易失性与最终领域的表现
【发布时间】:2020-03-20 23:20:22
【问题描述】:

什么对性能更有利?假设没有或几乎没有争吵

  1. 具有可变字段并一一更新的可变类
  2. 具有最终字段的不可变类,更新周期避免了 多字段更新,而是重新创建一次类

Volatiles 在每次写入时都需要内存屏障,我想 final 字段也是如此?意味着对象构造时的单一内存屏障?

更新说明:

考虑到 java 内存模型和当前的一代硬件,我觉得这个问题本身很有价值,并且可以笼统地回答。如果你必须假设细节:

  • 对象当然是从多个线程访问的,否则这个练习将毫无意义
  • 单个对象的寿命很长,例如几个小时
  • 这些对象有成百上千个,每秒有成百上千个更新事件

【问题讨论】:

  • 尝试写入最终字段将在编译时被捕获。
  • 没有足够的信息来回答这个问题。这取决于你对这些对象做了什么。不可变对象通常是一个很好的设计,但如果您要进行大量更改,频繁创建数百个新的不可变对象可能会影响性能。仅当您从不同线程修改对象时,可变字段才重要。
  • 如果您已经完成了足够的分析来担心这一点,那么您已经完成了足够的分析以获得数据来回答您的问题。这闻起来像双加过早的优化。

标签: java concurrency memory-model


【解决方案1】:

final 提示编译器字段值不能更改。任何写尝试都会在编译时被捕获。读取final 值不使用内存屏障。您不能写入final 变量,因此内存屏障毫无意义。

使用提示,编译器(或 JIT)可以用常量替换对最终值的内存引用。所以在性能方面,final 不会引入任何额外的开销。

【讨论】:

    【解决方案2】:

    如果垃圾收集器在最后一次访问旧对象和为新对象提供可用空间之间刷新每个线程的缓存,并且如果没有缓存行包含来自多个对象的数据,那么在大多数平台上它将是在对该对象的引用存储在该线程可访问的位置之前,新构造的对象自然不可能被加载到任何线程的缓存中,即使在没有任何读取障碍的情况下(超出前面提到的每 GC 循环一次的系统范围屏障)。此外,如果编译器可以判断一个对象的多个字段发生写入,而不会对引用可能已暴露的任何其他对象进行任何干预写入,则它可以省略除最后一个之外的所有写入障碍。

    唯一一次使用final 字段会比volatile 更昂贵,如果它需要创建更多对象来处理可以使用volatile 字段“就地”完成的更改。由于许多因素都会影响对象创建成本,因此判断在特定系统的特定情况下哪种方法更有效的唯一可靠方法通常是对两者进行基准测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多