2013-01-14 38 views
2

讀一本書,這個代碼出現:的Java指令重新排序/緩存線程

public class Test { 

    private static boolean ready = false; 
    private static int number = 0; 

    public static class ListenerThread extends Thread { 

     public void run() { 

      while(!ready) { 
       Thread.yield(); 
      } 
      System.out.println(number); 

     } 

    } 

    public static void main (String[] args) { 

     new ListenerThread().start(); 
     number = 10; 
     ready = true; 

    } 

} 

主要觀點我很驚訝被筆者比較快提及。

  1. 他們說ListenerThread可能永遠不會終止。我想了幾天(在我的腦海裏),我唯一的結論是它可能被ListenerThread緩存。真的嗎?使ready不穩定地解決問題(因爲它不應該緩存它)?

  2. 他們也表示程序可能會打印0.現在我明白了Java可能會重新排列指令,因此在數字更改之前,準備好會變爲另一個線程。有沒有什麼方法(技術),除了把這些指令放在同步塊中解決問題(在中心鎖定值上)?我想也許實現notify()/ wait(),但我覺得它會遭受同樣的後果。避免這個問題的最好方法是什麼?

謝謝!

編輯:

我只是覺得,我已經經歷了很多代碼看,很少煩惱,以防止在多線程重新排序。這有多常見?

回答

7

我唯一的結論就是它可能被該ListenerThread緩存。真的嗎?準備好揮發物能解決問題(因爲它不應該緩存)?

不僅被緩存,而且JIT可以在線程永不改變它的基礎上內聯值。即它變得硬編碼。

使用volatile可以防止這樣的假設。它會強制它每次讀取緩存一致的副本。

我現在明白了,Java的可能重新排序的說明,

不僅Java的,但CPU可以指令重新排序。 JIT意識到CPU可以做這種重新排序,而且它很少需要AFAIK,因爲它假設CPU會做得很好。

順便說一句,訪問一個volatile變量還會阻止指令重新排序,所以使得volatile變量解決了這兩個問題。

3

他們說ListenerThread可能永遠不會終止。我想了幾天,我唯一的結論是它可能會被該ListenerThread緩存。真的嗎?準備好揮發物能解決問題(因爲它不應該緩存)?

是的,是的。是的,聲明變量volatile會更改變量的內存訪問語義,並在每次訪問該變量時強制重新讀取。

他們還表示程序可能會打印0.現在我明白了Java可能會重新排列指令,因此在數字更改之前,準備就緒會變爲另一個線程。除了將這兩條指令放在同步塊中解決問題之外,還有什麼方法嗎?我想也許實現notify()/ wait(),但我覺得它會遭受同樣的後果。

這是因爲沒有的訂購保證,因此JVM可以自由地重新排序變量賦值。如果你想讓number對雙方同樣可見,你必須防止這種重新排序,正如@PeterLawrey所說的那樣,使得ready變得足夠。

+0

OP沒有提到在'Test'上同步,而是使用'synchronized'塊(可能在某個將會工作的相互鎖定)。 – yair

+0

@yair,是的,我想將隨時可用的鎖定變量中的準備檢查和打印以及變量賦值放入同步塊中,但這看起來過於緩慢。 – jn1kk

+0

是的,它只是不清楚OP「同步塊」的含義。編輯。 – fge