混合使用易失性和非易失性

3

我的问题适用于一个最初为null的字段,然后初始化为非null值,之后不再更改。

由于该字段需要尽快对所有线程可用,我需要使用volatile

但如果我想尽可能避免volatile访问的开销(即在非volatile字段足够时),以下代码是否有意义?

public class User {
  private String nonVolatileName;
  private volatile String volatileName;

  public String getName() {
    final String name = nonVolatileName;
    return name != null ? name : volatileName;
  }

  private void initName() {
    volatileName = nonVolatileName = expensiveComputation();
  }

  …
}

你的getName()方法必须是同步的,这也是一种额外负担。 - Lev Leontev
1
@LeoLeontev 我不明白为什么 getName 必须同步。 - Julien Royer
1
"name != null ?" 也是一种开销。您确定 volatile 的开销更大吗?(我认为一些巧妙的基准测试应该能够证明这一点)。 - Suma
@Suma 我同意,我需要运行基准测试。 - Julien Royer
1个回答

3

是的,代码可以正常工作,因为“对volatile字段的写入(§8.3.1.4)发生在随后对该字段的每个读取之前。” [1]

return name != null ? name : volatileName;

只要name == null,你就会强制从一个易失变量中读取数据,直到它不再是null(即expensiveComputation计算完成),此时的happen before语义保证了你将从此后看到nonVolatileName中的非null值。参见[2]。但是要向同事解释这个问题就有点难度。
[1] https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5-320 [2] Volatile variables and other variables

谢谢您的详细回答!在Java中有更简单的方法吗? - Julien Royer
2
这很丑陋,不是吗?您真的有使用/不使用 volatile 会产生实质性差异的用例吗? - David Soroko
在知识层面上,这似乎可以有所改变,因为该领域被频繁访问;但是如Suma所建议的,我需要运行基准测试来证明它是否有效。 - Julien Royer
1
如果你有时间,请在评论区发布基准测试结果,我很想知道你会看到什么。 - Suma
@Suma,基准测试在这里:https://github.com/julienroyer/mixing-volatile-and-non-volatile - 由于我是一个完全的基准测试工具新手,所以可能会有错误。我得到的结果(请参见结果目录)表明差异不明显。 - Julien Royer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接