【问题标题】:Access to volatile fields through local variables通过局部变量访问 volatile 字段
【发布时间】:2013-04-12 15:29:03
【问题描述】:

这个问题在某种程度上是这个问题的延续和扩展,我认为完美问题:How does assigning to a local variable help here?

此问题基于Effective JavaItem 71,建议在volatile 字段访问中引入局部变量以提高性能:

private volatile FieldType field;  
FieldType getField() {  
    FieldType result = field;  
    if (result == null) { // First check (no locking)  
        synchronized(this) {   
        result = field;  
        if (result == null) // Second check (with locking)  
            field = result = computeFieldValue();  
        }  
    }  
    return result;  
}

所以,我的问题比较常见:

我们是否应该始终通过将值分配给局部变量来访问 volatile 字段? (为了归档最佳性能)

即一些成语:

  1. 我们有一些volatile 字段,就叫它volatileField

  2. 如果我们想在多线程方法中读取它的值,我们应该:

    1. 创建具有相同类型的局部变量:localVolatileVariable
    2. 为易失字段赋值:localVolatileVariable = volatileField
    3. 从此本地副本中读取值,例如:

      if (localVolatileVariable != null) { ... }
      

【问题讨论】:

  • 该问题与您链接的问题有何不同?似乎您希望对比那里(和这里)显示的特定代码段的“初始化延迟加载字段”场景更广泛的情况有一个答案。但恐怕答案只能是“取决于”。
  • 不同的是,我只想了解:这种优化是否仅适用于Item 71 中描述的特定情况,还是volatile 字段读取的常用方法?

标签: java performance concurrency volatile


【解决方案1】:

如果您计划执行任何类型的多步逻辑(当然,假设该字段是可变的),您必须将 volatile 变量分配给本地字段。

例如:

volatile String _field;

public int getFieldLength() {
  String tmp = _field;
  if(tmp != null) {
    return tmp.length();
  }
  return 0;
}

如果您没有使用 _field 的本地副本,则值可能会在“if”测试和“length()”方法调用之间发生变化,从而可能导致 NPE。

除了通过不进行多次易失性读取来提高速度的明显好处之外。

【讨论】:

    【解决方案2】:

    硬币有两个面。

    一方面,对 volatile 的赋值就像一个内存屏障,JIT 不太可能使用 computeFieldValue 调用重新排序赋值。

    另一方面,理论上这段代码会破坏 JMM。因为由于某些原因,一些 JVM 被允许重新排序 computeFieldValue 赋值,你会看到部分初始化的对象。只要变量读取不与变量写入顺序,这是可能的。

    field = result = computeFieldValue();  
    

    之前不会发生

    if (result == null) { // First check (no locking) 
    

    只要 Java 代码应该是“一次编写,到处运行”,DCL 就是一种不好的做法,应该避免。此代码已损坏,不在考虑范围内。

    如果您在一个方法中多次读取 volatile 变量,通过首先将其分配给局部变量,您可以最大限度地减少此类读取,这会更昂贵。但我不认为,你会得到性能提升。这可能是理论上的改进。这种优化应该留给 JIT,而不是开发人员考虑的重点。我同意this

    【讨论】:

    • 一方面分配给一个 volatile 作品 -> 他不是在写一个 volatile,他在读一个。
    • "field = result = computeFieldValue()" 他在这里写道。这是经典 DCL 反模式的变体。
    • fyi,OP 中显示的 DCL 模式没有损坏(它是“固定”版本)。从你的帖子中不确定你是否暗示它是正确的。
    • Java 中有一些东西在起作用,但你应该避免它们。 DCL 就是其中之一。
    • @Mikhail docs.oracle.com/javase/specs/jls/se7/html/… "如果 x 和 y 是同一个线程的操作,并且 x 在程序顺序中位于 y 之前,那么 hb(x, y)。"这意味着“if (result == null)”确实发生在“field = result = computeFieldValue();”之前。
    【解决方案3】:

    他没有可能对 volatile 变量进行 两次 读取,而是只进行一次。 读取 volatile 可能比通常的变量要慢一些。但即便如此,我们在这里谈论的是纳秒。

    【讨论】:

    • 好吧,但是 Joshua Bloch 说他的机器性能提高了 25% :)
    • @Andremoniy 那是他前段时间写的。 1 纳秒的 25% 仍然很少。对我来说太少了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多