2017-10-21 104 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