2013-11-02 156 views
7

背景

我想明白爲什麼一個代碼片段確實拋出NullPointerException。爲什麼此代碼不會拋出NullPointerException?

源代碼

考慮下面的代碼:

public class Agent { 
    public List files = new ArrayList(); 

    public void deliver() { 
    if(files != null && files.iterator().hasNext()) { 
     File file = (File)files.iterator().next(); 
    } 

    files = new ArrayList(); 
    } 
} 

deliver方法反覆調用,而下面的代碼在一個單獨的線程中運行:

public void run() { 
    agent.files = null; 
    } 

只有一個單個agent實例。

問題

從不拋出NullPointerException。

然而,當deliver方法停頓,甚至是0毫秒,符合市場預期則拋出NullPointerException:

public void deliver() { 
    if(files != null) { 
     Thread.currentThread().sleep(0); 

     if(files.iterator().hasNext()) { 
     File file = (File)files.iterator().next(); 
     } 
    } 

    files = new ArrayList(); 
    } 

我的理解是,有,在理論上,檢查files == null和調用之間的競爭條件files.iterator().hasNext()。在實踐中,我不能引入競爭條件而不引入暫停(即將後續方法調用中的空檢查拆分)。

問題

爲什麼第一deliver方法當空校驗和使用在同一語句組合不會拋出異常?

+1

你可以發佈'javap'輸出在適當的周圍比賽條件區域嗎? – hexafraction

+8

當你製作'files'' volatile'時會發生什麼? – OldCurmudgeon

回答

5

兩件事情:

  1. 的Thread.sleep(0)還是暫停執行(可能大於0毫秒)。基本上,即使0睡眠也會導致該線程暫時停止執行,然後重新啓動。這給了另一個線程一個運行和完成的機會,這就是爲什麼你能夠觸發競爭條件。

  2. 文件應該是volatile,否則允許JVM以這樣一種方式進行優化,即您可能永遠不會注意到它正在更改值,因爲它不認爲需要維護線程之間的一致性。

+0

這是否意味着JVM可以將空檢查和後續使用視爲原子操作,除非顯式設置爲volatile? –

+1

一個線程可以在你的情況下擁有一個像文件這樣的變量的本地副本。因此,將文件設置爲該線程外的其他內容對本地變量沒有影響。線程最終會檢查外部修改,但在您的情況下,可能在文件始終設置爲值的位置。易失性將確保線程_always_檢查外部修改。所以:不,它不會將其視爲原子操作,您只需在您的JVM中的計算機上獲得幸運。 – TwoThe

相關問題