2016-07-03 21 views
3

所以這裏是代碼。 基本上,如果我們改變ReadCalculation和Calculator類來擴展Thread而不是實現Runnable,我們需要實例化這些類並將它們傳遞給一個新的線程對象,或者只需在它們上調用start()。實現Runnable時不同的行爲,而不是延長線程

Calculator calc = new Calculator(); 
new ReadCalculation(calc).start(); 
new ReadCalculation(calc).start(); 
calc.start(); 

沒什麼特別的,到目前爲止..但是,當你執行這個小程序,有一個巨大的機會,你的線程將保持阻塞「等待計算......」如果我們上延伸渡過了Runnable接口的實現Thread類。

如果我們擴展Thread類而不是實現Runnable,那麼行爲是正確的,沒有任何競爭條件。 任何想法都可能是此行爲的來源?

public class NotifyAllAndWait { 

public static void main(String[] args) { 

     Calculator calc = new Calculator(); 
     Thread th01 = new Thread(new ReadCalculation(calc)); 
     th01.start(); 
     Thread th02 = new Thread(new ReadCalculation(calc)); 
     th02.start(); 

     Thread calcThread = new Thread(calc); 
     calcThread.start(); 
    } 
} 

class ReadCalculation implements Runnable { 

    private Calculator calc = null; 
    ReadCalculation(Calculator calc) { 
     this.calc = calc; 
    } 

    @Override 
    public void run() { 
     synchronized (calc) { 
      try { 
       System.out.println(Thread.currentThread().getName() + " Waiting for calculation..."); 
       calc.wait(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      System.out.println(Thread.currentThread().getName() + " Total: " + calc.getTotal()); 
     } 
    } 
} 

class Calculator implements Runnable { 
    private int total = 0; 
    @Override 
    public void run() { 
     synchronized(this) { 
      System.out.println(Thread.currentThread().getName() + " RUNNING CALCULATION!"); 
      for(int i = 0; i < 100; i = i + 2){ 
       total = total + i; 
      } 
      notifyAll(); 
     } 
    } 
    public int getTotal() { 
     return total; 
    } 
} 
+0

我不能引用語言或JVM規範的任何部分,這可能在這裏,但最可能的實際原因是'Thread'源代碼在'Thread'上佔用了很多鎖。類'和當前'線程'實例。 'calc'的內部和外部都可以鎖定Calculator實例。當你使用專用目標Runnable時,這些鎖可能不會干擾'Thread'內部的鎖定(只是選中:Android Thread實現中沒有任何代碼會鎖定傳遞的Runnable),但當擴展Thread時肯定會影響執行順序。 – user1643723

回答

2

implements Runnable版本,至少,你什麼都不做,以確保ReadCalculation線程達到Calculator線程進入其​​塊wait()之前。如果Calculator線程先輸入其​​塊,則在ReadCalculation線程調用wait()之前它將調用notifyAll()。如果發生這種情況,那麼notifyAll()是無效的,並且ReadCalculation線程將永遠等待。 (這是因爲notifyAll()只關心是已經等待對象線程;它設置任何種類的,可以通過後續調用wait()被檢測對象上指示器的。)

爲了解決這個問題,你可以將屬性添加到Calculator可用於檢查所做的煩躁,並且只調用wait()如果Calculator完成:

if(! calc.isCalculationDone()) { 
    calc.wait(); 
} 

(需要注意的是,爲了完全避免比賽條件,整個if語句來是的​​塊,而Calculator設置該屬性的​​塊調用notifyAll()是很重要的。你知道爲什麼嗎?)

(順便說一下,彼得Lawrey的評論說,「一個線程可以輕鬆地下載到100次迭代之前,其他線程甚至開始」是極具誤導性,因爲在你的程序中的100次迭代都發生Calculator已輸入​​區塊。由於ReadCalculation線程被阻止進入他們的​​塊並且調用calc.wait(),而Calculator在​​塊中,所以它應該是1次迭代,100次迭代還是1,000,000次迭代應該沒有關係,除非它具有有趣的優化效果,可以改變這一點前的程序的時間。)


您還沒有發佈整個extends Thread版本,但如果我理解正確的是什麼樣子,那麼它實際上仍然具有相同的競爭條件。然而,根據競爭條件的性質,微小的變化可能會嚴重影響不正當行爲的可能性。即使它看起來從來沒有出現過錯,仍然需要修正競爭條件,因爲幾乎可以肯定,如果您運行該程序的次數足夠多,它會偶爾會出現不正常行爲。

我沒有一個很好的解釋,爲什麼這種不正當行爲似乎比一種方法更頻繁地發生,但正如上面user1643723註釋,extends Thread的方法意味着大量的代碼其他也可能會鎖定您的Calculator實例;這可能會有某種效果。但說實話,我認爲值得擔憂的是,爲什麼競爭條件會更頻繁地或不經常地引起不良行爲;無論如何,我們必須解決它,故事的結尾。


順便提及:

  • 上面,我使用if(! calc.isCalculationDone());但實際上這是一個最佳做法,總是將調用wait()包裝在合適的while -loop中,所以你應該寫。這有兩個主要的原因:

    • 在平凡的計劃,你不一定知道爲什麼notifyAll()叫,或者即使你做什麼,你不知道這個原因是否仍然適用的等待線程實際喚醒的時間並恢復​​-lock。如果您使用while(not_ready_to_proceed()) { wait(); }結構來表達wait_until_ready_to_proceed()的想法,而不是僅僅編寫wait()並試圖確保沒有任何事情會導致它返回,而我們可以使用它來更容易地推斷您的交互的正確性,沒有準備好。

    • 在某些操作系統上,向進程發送信號將喚醒所有線程,即wait() -ing。這叫做虛假喚醒;有關更多信息,請參閱"Do spurious wakeups actually happen?"。因此,即使沒有其他線程稱爲notify()notifyAll(),線程也可能會被喚醒。

  • for -loop在Calculator.run()不應該在​​塊,因爲它不需要任何同步,所以不需要的爭用。在你的小程序中,它實際上沒有什麼區別(因爲無論如何,其他線程實際上沒有任何事情要做),但最好的做法是儘量減少​​塊內的代碼量。

2

當您執行這wait()需要在狀態改變後,你進行的notify()塊的循環。例如

// when notify 
changed = true; 
x.notifyAll(); 

// when waiting 
while(!changed) 
    x.wait(); 

如果您不這樣做,您將遇到諸如wait()虛假地喚醒或notify()丟失等問題。

注意:在其他線程啓動之前,線程可以很容易地進行100次迭代。預先創建Thread對象可能會對性能產生不同影響,以改變您的案例中的結果。

+1

儘可能正確,您的第一個陳述在這裏看起來不相關。 – user1643723

+0

@ user1643723很高興能夠通過更好的解答OPs問題得到解決。 –

+0

@PeterLawrey謹慎地闡述「通知迷失」部分? [關於notify()]的JLS章節(https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.2.2)沒有提及任何類似的內容。 – user1643723

相關問題