2015-05-27 156 views
5

運行此代碼,我希望它將測試變量增加5秒,然後完成。定時while循環不終止

import java.util.Timer; 
import java.util.TimerTask; 

public class Test { 
    private static boolean running; 

    public static void main(String[] args) { 
     long time = 5 * 1000;  // converts time to milliseconds 
     long test = Long.MIN_VALUE; 
     running = true; 

     // Uses an anonymous class to set the running variable to false 
     Timer timer = new Timer(); 
     timer.schedule(new TimerTask() { 
      @Override 
      public void run() { running = false; } 
     }, time); 

     while(running) { 
      test++; 
     } 

     timer.cancel(); 
     System.out.println(test); 
    } 
} 

但是,當我運行它的程序沒有結束(我假設,我給了它一個合理的時間)。但是,如果我將while循環更改爲

while(running) { 
     System.out.println(); 
     test++; 
    } 

程序在預計的時間內完成(並打印出很多行)。我不明白。爲什麼會發生這種行爲?

+0

如果你運行'volatile'會怎麼樣? –

+0

我剛剛使用JDK 7和IntelliJ 11測試了您的代碼,並在幾秒鐘內完成了兩個版本。你用什麼來運行? –

+0

@AndyTurner哇,我從來沒有遇到過揮發性關鍵字。這使代碼工作。謝謝! – DenverCoder9

回答

4

根據Java Memory Model無法保證何時可以從另一個線程看到非易失性字段。在你的情況下,你的主要方法是JIT編譯和JIT編譯器合理地假設當前線程不會修改running變量在循環中,那麼我們不應該在每次迭代讀取它,並將循環轉換爲無限循環。關於這種情況甚至有FindBugs warning

當您添加一個System.out.println調用時,似乎JIT編譯器不能內聯它,因此它不能確定此方法不會修改running變量,因此它會關閉字段讀取優化。但是,這不應該被視爲問題解決方案:即使您有內部的System.out.println,新版本的Java可能會更加智能,並優化您的循環。

+0

我接受了這個答案,因爲它鏈接了額外的信息。謝謝! – DenverCoder9

2

讓我們接近你的代碼...

如果你調試你的代碼,它會工作。那是因爲你只會看到一個線程而沒有緩存。 Java可以使用基於線程的緩存機制。你需要讀取和寫入主內存。

所以如果你在你的運行變量上使用volatile關鍵字,jvm將會看到它可以被多線程編輯,並且它不應該被緩存。

因此,在您的情況下解決方案是將volatile添加到您的運行變量。

2

您正在更新與main方法不同的線程中的running變量。 Java內存模型是這樣的,你不能保證看到更新不同線程中的非易失性變量。

最簡單的解決方案是使變量volatile:這迫使變量的'當前'值在線程訪問它時被檢查。

其他解決方案包括使用AtomicBoolean代替boolean,和包裝中相互​​塊(即訪問running代碼相同的監視器作爲更新它的代碼上同步)訪問running

我強烈建議您選擇一份Java Concurrency In Practice,它詳細描述了這個問題。