2017-10-21 56 views
0

为什么这里需要局部变量我不明白:https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java 我们能有什么问题,如果我们没有这样的:的Java懒线与最终的现场实施安全单

public class FinalWrapper<T> { 
    public final T value; 
    public FinalWrapper(T value) { 
     this.value = value; 
    } 
} 

public class Foo { 
    private FinalWrapper<Helper> helperWrapper; 

    public Helper getHelper() { 
     FinalWrapper<Helper> tempWrapper = helperWrapper; 

     if (tempWrapper == null) { 
      synchronized(this) { 
       if (helperWrapper == null) { 
        helperWrapper = new FinalWrapper<Helper>(new Helper()); 
       } 
       tempWrapper = helperWrapper; 
      } 
     } 
     return tempWrapper.value; 
    } 
} 

我从得到这个代码局部变量?根据维基文章撰写:Semantics of final field in Java 5 can be employed to safely publish the helper object without using volatile. The local variable tempWrapper is required for correctness: simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model. Performance of this implementation is not necessarily better than the volatile implementation.

在此先感谢。

+0

您是使用嵌套的私有类,它通过类加载机制强制执行单行为的懒惰持有者成语更好。 –

+0

我已在下面更新了我的答案,以提供有关重新排序的更多信息。我希望它耗尽了这个话题。 – cbartosiak

回答

1

要了解潜在的问题,让我们从代码中删除局部变量:

public class Foo { 
    private FinalWrapper<Helper> helperWrapper; 

    public Helper getHelper() { 
     if (helperWrapper == null) { 
      synchronized(this) { 
       if (helperWrapper == null) { 
        helperWrapper = new FinalWrapper<Helper>(new Helper()); 
       } 
      } 
     } 
     return helperWrapper.value; 
    } 
} 

我们有三个读取在这种情况下:

  1. 外空检查。
  2. 内部的空检查。
  3. 在退货之前阅读。

的问题是,由于读出的重新排序所述第一读取可以返回非空值和第三读取可以返回。这意味着第三读第一位的,这是为了确保helperWrapper初始化之前发生的......

添加局部变量解决了问题,因为我们helperWrapper值赋给tempWrapper,然后它并不重要读取什么顺序tempWrapper。如果它有一个非空值,则它用于空检查和return语句。

它可能发生是因为Java内存模型允许为了优化目的对操作进行重新排序。看看here的报价:

什么意思是重新排序?

有许多箱子在其访问可 出现在一个不同的顺序是由 程序指定要执行到程序变量 (对象的实例字段,类静态字段,和数组元素)。编译器可以自由地以最优化的名义排序 指令。在某些情况下,处理器可能会执行 指令。数据可能是 在 寄存器,处理器高速缓存和主存储器之间移动的顺序与程序指定的顺序不同。

[...]

编译器,运行时和硬件都应该合谋创建 作为-如果串行语义,这意味着错觉,在一个 单线程程序,该程序不应该能够观察重排序的效果。但是, 错误同步的多线程程序可能会重新排序,其中一个线程是 能够观察其他线程的影响,并且可能能够 检测到变量访问对于其他线程而言以 不同于执行的顺序变得可见或在程序中指定。

[...]

+0

感谢您的回答,@cbartosiak!不幸的是,我不明白这个具体的重新排序是可能的。让我们假设我们分析没有局部变量的代码(你写的)。如果我们假设这样的重新排序是可能的(第三次读取在第一次之前完成),并且如果我们在单线程程序中运行它,它将抛出NullPointerException异常(因为变量在第二次读取后被初始化)。那么这怎么可能?我相信你,因为我在这里看到过一个类似的例子:http://jeremymanson.blogspot.bg/2008/12/benign-data-races-in-java.html(最后一个例子) – DPM

+0

但是这怎么可能当这种重新排序可以改变单线程执行? 在此先感谢! – DPM

+0

在单线程执行中,顺序无关紧要,因为helperWrapper的值不能在读取之间更改。该问题出现在多线程执行中,其中另一个线程可以在读取之间写入helperWrapper。它有帮助吗? – cbartosiak