2013-08-18 117 views
0

我是新手,當發現問題時發現併發和不確定自己時,我正在查看相當成熟的代碼庫,並找到了下面的代碼(爲了簡潔起見),我相信易受數據種族:多線程代碼中的非易失性狀態標誌

public class Example extends Thread { 
    boolean condition = false; 

    public void run() { 
     while (true) { 
      synchronized (this) { 
       try { 
        while(condition) wait(); 
       } 
       catch (InterruptedException e) { /*for brevity*/ } 
      } 

      // non-blocking computation 
     } 
    } 

    public void setTrue() { condition = true; } 

    public void setFalse() { 
     synchronized (this) { 
      condition = false; 
      this.notifyAll(); 
     } 
    } 
} 

據我理解,因爲即使在synchronized塊,編譯器不會發出任何內存屏障condition必須是揮發性的;如果這是一個不穩定的商店conditionsetTrue編譯器會發出StoreEnter.

我是否有權相信上述容易受到數據競爭?如果是的話,我怎麼能通過一個例子來見證數據競賽(而不是簡單地知道JMM提供的保證)。線程中隨機調用setTrue的簡單測試不會發現數據競爭。

此外,我相信使用notifyAll在這裏是過度殺傷,因爲有一個條件來檢查,只有一個線程將永遠等待它,對嗎?

謝謝。

+0

除非您確實有理由,否則不要擴展Thread類。最好是實現Runnable接口並向Thread構造函數提供你的類的一個實例。 – gparyani

回答

3

就我所知,條件必須是易失性的,因爲即使使用同步塊,編譯器也不會發出任何內存障礙;如果它是一個不穩定的存儲區以在setTrue中進行調節,則編譯器會發出StoreEnter。

這是不正確的。在​​塊中使用共享變量時,對於使用相同鎖定的相同變量的其他線程,您的代碼將是線程安全的。如果需要記憶障礙,那麼它們將被使用。

但是,你對我們的代碼是是不正確因爲setTrue()方法更新​​塊之外的標誌。


我說得對不對相信上面的易受數據爭?

是的......有點。情景如下:

  1. 條件是false
  2. 一些其他線程調用setTrue它將條件變量設置爲其緩存中的true。但由於setTrue方法不使用​​,因此不存在寫入障礙,也不會刷新到主內存。
  3. 「示例」線程從主內存(仍然是false)獲取最新的提交值,並且不會按照它應該執行的操作等待。

而且,我認爲使用notifyAll的是這裏矯枉過正,因爲有一個條件檢查,只有一個線程將等待就可以了,對不對?

它可以替換爲notify() ......如果這是你的意思。但說實話,你使用通知的風格並沒有真正的區別。


你的評論:

我的意思是,編譯器會不會認爲有必要在這種情況下提交內存屏障。

也許吧。但「monitorenter」和「monitorexit」指示隱含地涉及記憶障礙。

和:

豈不也是正確的,如果條件爲揮發性?

如果你在談論使用volatile和​​,然後是這將是正確的......雖然volatile是多餘的(假設setTrue bug修復。)

如果你是在談論只有volatile,那麼沒有。您只能使用volatile來實現高效的「等待條件變量」。問題是「讀/測試/等待」或「寫/通知」序列都不能原子執行;即沒有比賽條件的可能性。

此外,如果不使用原始對象互斥對象或Lock對象,則無法執行與wait/notify等效的操作。

+0

我的意思是編譯器不會認爲有必要在這種情況下提交內存屏障。如果情況不穩定,這也不正確嗎? – bluth

0

我是否有權相信上述情況容易發生數據競爭?

不這麼認爲。條件不重要,它只允許該方法避免等待。它的設置方式也並不重要。它不需要變化,因爲它對一個對象是本地的。

而且,我認爲使用notifyAll的是這裏矯枉過正,因爲有 一個條件檢查,只有一個線程將等待就可以了, 吧?

notifyAll的是好的,而只有一個線程在等待該方法可以有許多其他線程正在等待,或者等待時,線程。