2013-09-30 82 views
2

試圖圍繞此代碼包裹我的頭。當我運行這個 - 輸出將是Roger。是不是味精是一個靜態變量,並在課堂上因此應該打印摩爾線程和靜態變量

編輯:我已經允許睡眠也允許子線程運行其過程。它還打印打印..。仍然沒有變化

public class Test2 { 
    private static String msg = "Roger"; 

    static { 
     new Thread(new Runnable() { 
      public void run() { 
       System.out.println("printing.."); 
       msg += "Moore"; 
      } 
     }).start(); 
    } 

    static { 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
     } 
    } 

    public static void main(String argv[]) { 
     System.out.println(msg); 
    } 
} 
+1

它看起來像一個簡單的競爭條件。我想知道,在main()中,如果你睡一秒鐘,它會輸出「Moore」而不是「Roger」嗎? – CmdrMoozy

+0

是的 - 就是這樣..加入一個Thread.sleep更長的時間沒有打印相關的輸出!感謝你的回答!請添加你的答案,以便我可以「回答」它..或者我可以回答papmplhet - 兩個都是正確的... – user2796381

回答

4

試圖圍繞此代碼包裹我的頭。當我運行這個時 - 輸出將是羅傑。是不是msg是一個靜態變量,因此在課堂上應該打印Moore?

正如其他人指出的那樣,這是一種競爭條件,但這個簡單的答案更復雜。

編輯:我已經允許睡眠也允許子線程運行其過程。它還打印打印...仍然沒有變化

當一個類被初始化時,static代碼在首先訪問類的線程中執行 - 在這種情況下是主線程。所有其他線程必須等待此初始化完成才能訪問該類。這意味着後臺線程實際上停止並且等待的類初始化完成,然後它可以執行msg += "Moore";。然後,在main打印之前,查看信息是否分配給"Roger"並且後臺線程可以附加到。即使msg字段爲volatile,比賽依然存在。您可以從JLS section 12.4.2 on Detailed Initialization Procedure中瞭解過程的複雜性。

所以發生大約是:

  1. 主線程初始化Test2類。
  2. msg首先被初始化,因爲它在static塊之前。
  3. 第一個static塊被執行,它分叉後臺線程。
  4. 第二個static塊被執行,sleep()阻止初始化線程。
  5. 後臺線程開始運行(可能是以前的步驟之前)。它會更新msg,但是因爲主線程正在休眠並且尚未完成類初始化,所以該類被鎖定。後臺線程必須等待。
  6. 主線程喚醒並完成初始化。
  7. 這釋放上,其允許在後臺線程繼續類的塊。
  8. 同時與前一步驟,main被調用,它是一個爭用條件,看是否能夠msg它打印出來之前被更新。

一般來說,在static這樣的方法中分叉後臺線程是極其令人不悅的。顯然不建議將sleep置於static塊中。如果主線程首先aquires鎖

public class Test { 
    private static String msg = "Roger"; 
    private static volatile boolean done = false; 
    private static final Object lock = new Object(); 
    static { 
     new Thread(new Runnable() { 
      public void run() { 
       synchronized(lock) 
       { 
        lock.notify(); 
        System.out.println("printing.."); 
        msg += "Moore"; 
        done=true; 
       } 
      } 
     }).start(); 
    } 

    public static void main(String argv[]) { 
     synchronized(lock) 
     { 
      while(!done) 
      { 
       try { 
        lock.wait(); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
      } 
     } 
     System.out.println(msg); 
    } 
} 

,那麼它將msg.wait

+0

+1:有趣。 JSL中有關於發生了什麼的章節? –

+0

找到了@MartijnCourteaux。有關詳細初始化過程的第12.4.2節。 http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2 – Gray

+0

我正在讀一兩分鐘的那款,但我認爲這更多的是WHO(=哪個線程)正在初始化該類,而不是真正阻止對類成員的訪問。但是,我會重讀。 –

2

這是一種競爭條件。無法保證Runnable何時執行。

編輯:此答案響應原始發佈的問題,其中沒有延遲出現在靜態初始化器中。這導致讀取靜態成員的主線程和生成的線程更新它之間的簡單競爭條件。

+1

這似乎不是我的競爭條件。睡一秒鐘應該讓另一條線足夠的時間完成。檢查這個例子:http://ideone.com/DVMZ0f –

+0

@MartijnCourteaux,OP已經證實,延遲主線程改變了輸出。 – pamphlet

+0

對不起,但我是downvoting。此行爲是由於Java保護其他線程訪問未初始化類的變量而引起的。 –

2

直到你的類中的所有靜態初始值設定項完成後,纔會調用main方法。所以它會一直等到靜態入口完成。即使它有睡眠。

另外靜態初始化是線程安全的,所以你分叉的線程不能訪問變量,直到靜態init塊完成。

0

與其等待一點點,並希望其他線程運行,你可以用一些同步機制保障它。它將不會繼續,直到調用notify(實際上,它在包含通知的同步塊完成時繼續)。如果新線程首先獲得鎖,那麼主線程將不得不在其同步塊的開始處等待。一旦進入,完成將是真實的。它不會等待,直接通過。

+0

由於幾個原因,這不起作用。如果它在多個線程中更新,'done'應該是'volatile'。但真正的問題是,你正試圖在引用正在改變的對象上同步。這從來沒有工作。 – Gray

+0

@Gray''fixed'' – Cruncher

+0

如果可能的話,鎖定對象應該始終是'final'Cruncher。否則這看起來不錯。 – Gray