2016-01-07 81 views
5

在以下代碼中,如果我在循環內部使用sysout語句,那麼代碼將在條件滿足時執行並進入循環,但如果我不在內部使用sysout語句然後循環然後無限循環繼續而不進入if條件,即使if條件滿足..任何人都可以請幫我找出確切的原因。只是一個sysout語句使if條件成爲true。爲什麼這樣?由於system.out.println語句導致運行線程延遲

的代碼如下: -

class RunnableDemo implements Runnable { 
    private Thread t; 
    private String threadName; 

    RunnableDemo(String name){ 
     threadName = name; 
     System.out.println("Creating " + threadName); 
    } 
    public void run() { 
     System.out.println("Running " + threadName); 
     for(;;) 
     { 
      //Output 1: without this sysout statement. 
      //Output 2: After uncommenting this sysout statement 
      //System.out.println(Thread.currentThread().isInterrupted()); 
      if(TestThread.i>3) 
      { 
       try { 
        for(int j = 4; j > 0; j--) { 
         System.out.println("Thread: " + threadName + ", " + j); 
        } 
       } catch (Exception e) { 
        System.out.println("Thread " + threadName + " interrupted."); 
       } 
       System.out.println("Thread " + threadName + " exiting."); 
      } 
     } 
    } 

    public void start() 
    { 
     System.out.println("Starting " + threadName); 
     if (t == null) 
     { 
     t = new Thread (this, threadName); 
     t.start(); 
     } 

    } 

} 

public class TestThread { 
     static int i=0; 
    public static void main(String args[]) { 

     RunnableDemo R1 = new RunnableDemo("Thread-1"); 
     R1.start(); 
     try { 
     Thread.sleep(10000); 
    } catch (InterruptedException e) { 

     e.printStackTrace(); 
    } 

    i+=4; 
    System.out.println(i); 
    } 
} 

沒有系統輸出語句在無限循環輸出: - enter image description here

輸出與系統輸出語句在無限循環: - enter image description here

+2

我也會想你正在運行成揮發性的問題。嘗試製作 static int i = 0; static volatile int i = 0;如果現在的行爲與生病相同,則用解釋提出答案。 –

+0

@Aaron它是一個無限循環,所以他們不斷地執行它,正確...?編輯:亞倫離開大樓。 –

+1

@亨克德波爾,我嘗試使用易失性,它工作正常。但是,你能解釋一下sysout語句做了哪些改變,即使沒有使用volatile關鍵字,代碼也能工作。 –

回答

3

這裏的問題可以通過改變

static int i=0;

被固定到

static volatile int i=0;

製作一個變量volatile有許多複雜的後果,我不是這方面的專家。所以,我會試着解釋我如何看待它。

變量i住在你的主存,你的RAM。但是RAM速度很慢,所以你的處理器將它複製到更快(和更小)的內存中:緩存。事實上有多個緩存,但那是不相關的。

但是當兩個不同處理器上的兩個線程將它們的值放在不同的緩存中時,值發生了什麼變化?那麼,如果線程1更改了高速緩存1中的值,線程2仍然使用高速緩存2中的舊值。除非我們告訴兩個線程此變量i可能隨時都在變化,就好像它是魔術一樣。這就是volatile關鍵字的作用。

那麼,爲什麼它與print語句一起工作呢?那麼,print語句在幕後調用了很多代碼。這些代碼中的一些很可能包含一個同步塊或另一個volatile變量,這個變量也會刷新兩個緩存中的值i。 (感謝Marco13指出了這一點)。

下次嘗試訪問i時,會得到更新的值! PS:我在這裏說RAM,但它可能是兩個線程之間最接近的共享內存,如果它們是超線程的,那麼它可能是一個緩存。

這是一個很好的解釋太(含圖片!):

http://tutorials.jenkov.com/java-concurrency/volatile.html

+0

我還想問一件事,如果我使用thread.sleep而不是sysout語句,即使這樣if條件也成立。 Thread.sleep是否也會佔用一些內存並清除緩存中的數據? –

+0

@ Marco13好的,我會修改答案。感謝您的更正! –

+0

@MeenakshiKumari最好的方法是自己測試一下! :)但是,不,我不這麼認爲,除非(再次偶然),一些外來的代碼會在睡眠運行時觸發清除緩存。就像我說的,我不是專家,在實踐中線程通常比看起來複雜得多。所以我很猶豫在這裏給你非常具體的規則,對不起。睡覺本身就是告訴操作系統,你現在沒有什麼可做的事情了,沒有其他的了,所以你不應該依賴它有任何特殊的屬性,如刷新內存。 –

2

在訪問變量值時,cha nges不會每次寫入(或加載)實際的內存位置。該值可以加載到CPU寄存器或緩存,並在那裏坐直到緩存刷新。此外,因爲TestThread.i在循環內部沒有被修改,所以優化器可能會決定在循環之前用一個檢查來替換它,並且完全擺脫if語句(我不認爲它實際上發生在您的情況中,但重點是它可能可能)。

,使線程刷新其高速緩存和它們的物理內存的當前內容同步的指令被稱爲存儲器屏障。 Java中有兩種強制內存屏障的方法:輸入或退出​​塊或訪問volatile變量。 當其中任何一個事件發生時,緩存被刷新,並且線程保證能夠看到內存內容的最新視圖,並且具有它在本地提交到內存的所有更改。

所以,在意見提出,如果你申報TestThread.ivolatile,這個問題就會迎刃而解,因爲每當值被修改,更改的內容將立即提交,並且優化器將不知道要優化器,電子支票遠離循環,而不是緩存該值。

現在,爲什麼添加打印語句會改變行爲?好吧,io內部有很多同步進行,線程在某個地方碰到內存障礙,並加載新的值。這只是一個巧合。

+0

我並不是說你錯了,而是允許優化器對成員變量做出如此沉重的假設?似乎容易出錯。 –

+0

當然,爲什麼不呢?如果循環內部沒有內存屏障,並且當前線程未修改變量,則認爲它保持不變是完全安全的。事實上,做這個假設比不做它更安全,因爲它使行爲具有確定性。 – Dima

+0

是的,但是它在編譯時如何知道在線程產生之前我的值不會改變?也許我錯過了什麼。 –