2012-09-09 47 views
2

我試圖從JCIP和下面的程序示例不應該工作,但即使我執行它說的20倍它總是工作,這意味着readynumber變得即使它應該看到在這種情況下爲什麼這個破碎的程序總是運行?

public class NoVisibility { 
private static boolean ready; 
private static int number; 

private static class ReaderThread implements Runnable { 
    public void run() { 
     while (!ready) 
      Thread.yield(); 
     System.out.println(number); 
    } 
} 

public static void main(String[] args) { 
    System.out.println(Runtime.getRuntime().availableProcessors()); 
    //Number of Processor is 4 so 4+1 threads 
    new Thread(new ReaderThread()).start(); 
    new Thread(new ReaderThread()).start(); 
    new Thread(new ReaderThread()).start(); 
    new Thread(new ReaderThread()).start(); 
    new Thread(new ReaderThread()).start(); 

    number = 42; 
    ready = true; 
} 
} 

在我機總是打印

4 -- Number of Processors 
42 
42 
42 
42 
42 

According to Listing 3.1 of JCIP It should sometimes print 0 or should never terminate it also suggest that there is no gaurantee that ready and number written by main thread will be visible to reader thread

更新 我在主線程中添加了1000ms睡眠後,將所有線程的輸出保持相同。 我知道程序是壞了,我希望它表現得這樣

+6

爲什麼你認爲它不應該工作? –

+0

'private'變量對內部類可見。沒有理由不應該這樣做。 –

+0

JCIP書清單3.1建議它應該打印0或永遠運行。 –

回答

3

這項計劃被打破,因爲readynumber應聲明爲volatile
由於這樣的事實:readynumber是原始變量,對他們的操作都是原子保證他們會被其他線程可見。
看起來,調度程序在main之後運行線程,這就是爲什麼他們看到numberready被初始化。但那只是一個調度。
如果您添加了例如中的sleep以影響調度程序,您將看到不同的結果。
所以這本書是正確的,不能保證在單獨的線程中運行Runnable是否會看到變量被更新,因爲變量未被聲明爲volatile

更新:
這裏的問題是,由於缺乏volatile編譯器是免費閱讀領域ready只有一次,並在循環的每次執行重用的緩存值。
該計劃本質上是有缺陷的。和線程問題的問題通常,當您將應用程序部署到外地.... 從JSL出現:

例如,在下面的(壞)的代碼片段,假設 this.done是非易失性布爾字段:

while(!this.done)
Thread.sleep(1000);

編譯器可以自由讀取this.done字段一次,並在循環的每次執行中重用緩存值。這意味着即使其他線程更改了this.done的 值,循環也不會終止。

+0

在開始所有線程後,我在主線程中加入了1000ms睡眠時間,但睡眠結束後所有線程都會立即終止。 –

+1

_Might_可能會看到不同的結果,具體取決於睡眠時間。 JCIP試圖做的一點是,上面的多線程代碼不應該依賴於其他線程以任何線程安全的方式接收更新的值,假設它們運行_before_主線程能夠設置'number'和「準備好」字段。正如Cratylus指出的那樣,set操作是原子操作,但其他'Thread's的內存緩存失效並不能保證。我會注意到它應該可以在任何JVM上可靠地終止,但由於上述描述,它是不安全的。 – pickypg

+0

即使您添加了睡眠,也無法保證它會起作用或無法正常工作。它將取決於許多因素,包括JVM選項,機器架構,編譯器優化等。 – assylias

1

重要的是要記住的是,一個破碎的併發程序可能總是與JVM的選擇正確的組合,機器架構等等這並不使它成爲一個很好的計劃工作,因爲它可能會在失敗不同的上下文:沒有併發問題出現的事實並不意味着沒有任何併發​​問題。

換句話說,你不能證明併發程序在測試中是正確的。

回到你的具體例子,我看到了與你描述的相同的行爲。但是,如果我在while循環中刪除Thread.yield()指令,則8個線程中的3個會停止並打印42,但其餘的不會,程序也不會結束。