2010-05-01 16 views
-1

我正在創建一個圖形計算器。Java中的難以捉摸的競爭條件

爲了試圖從中獲得更多的性能,我在線計算器中添加了一些多線程。本質上,我目前的實現是構造X值的線程安全的Queue,然後啓動它所需的多個線程,每個線程使用隊列計算線上的點以獲取其值,然後使用HashMap排序點計算完成。這個實現很好,這不是我的競爭條件(僅僅是一些背景信息)的地方。

在檢查性能結果時,我發現HashMap是一個性能瓶頸,因爲我在一個線程上同步執行。所以我認爲按照計算的順序排列每個點會效果最好。我嘗試了PriorityQueue,但這比HashMap慢。

我結束了創建的算法,基本上是這樣的:

我構建的X值的列表來計算,就像我現在的算法。

然後,我將該值列表複製到另一個類中,缺乏想象力並暫時命名爲BlockingList,它負責在計算點時對點進行排序。

BlockingList包含一個put()方法,它採用兩個BigDecimal s作爲參數,第一個是X值,第二個是計算出的Y值。 put()將只接受一個值,如果X值是列表中下一個要在X值列表中接受的值,並且將阻塞,直到另一個線程給出下一個例外值。

例如,因爲這可能會令人困惑,比如我有兩個線程,Thread-1Thread-2Thread-2從值隊列中獲取X值10.0,並且Thread-1得到9.0。但是,Thread-1首先完成其計算,並在Thread-2之前呼叫put()。因爲BlockingList預計首先得到10.0,而不是9.0,它將阻止Thread-1直到Thread-2完成並呼叫put()。一旦Thread-2給出BlockingList10.0,它notify()所有等待的線程,並且預計接下來9.0。這一直持續到BlockingList獲得其所有預期值。

(我道歉,如果這是難以遵循,如果你需要更多的澄清,只是要求。)

正如預期的問題的標題,還有在這裏的比賽條件。如果我在沒有任何System.out.println的情況下運行它,它有時會因爲與wait()notifyAll()衝突而鎖定,但如果我放入println,它將運行良好。

小實現這個包含如下,並表現出相同的行爲:

import java.math.BigDecimal; 
import java.util.concurrent.ConcurrentLinkedQueue; 

public class Example { 
    public static void main(String[] args) throws InterruptedException { 
     // Various scaling values, determined based on the graph size 
     // in the real implementation 
     BigDecimal xMax = new BigDecimal(10); 
     BigDecimal xStep = new BigDecimal(0.05); 

     // Construct the values list, from -10 to 10 
     final ConcurrentLinkedQueue<BigDecimal> values = new ConcurrentLinkedQueue<BigDecimal>(); 

     for (BigDecimal i = new BigDecimal(-10); i.compareTo(xMax) <= 0; i = i.add(xStep)) { 
      values.add(i); 
     } 

     // Contains the calculated values 
     final BlockingList list = new BlockingList(values); 

     for (int i = 0; i < 4; i++) { 
      new Thread() { 
       public void run() { 
        BigDecimal x; 

        // Keep looping until there are no more values 
        while ((x = values.poll()) != null) { 
         PointPair pair = new PointPair(); 
         pair.realX = x; 

         try { 
          list.put(pair); 
         } catch (Exception ex) { 
          ex.printStackTrace(); 
         } 
        } 
       } 
      }.start(); 
     } 
    } 

    private static class PointPair { 
     public BigDecimal realX; 
    } 

    private static class BlockingList { 
     private final ConcurrentLinkedQueue<BigDecimal> _values; 
     private final ConcurrentLinkedQueue<PointPair> _list = new ConcurrentLinkedQueue<PointPair>(); 

     public BlockingList(ConcurrentLinkedQueue<BigDecimal> expectedValues) throws InterruptedException { 
      // Copy the values into a new queue 
      BigDecimal[] arr = expectedValues.toArray(new BigDecimal[0]); 

      _values = new ConcurrentLinkedQueue<BigDecimal>(); 
      for (BigDecimal dec : arr) { 
       _values.add(dec); 
      } 
     } 

     public void put(PointPair item) throws InterruptedException { 
      while (item.realX.compareTo(_values.peek()) != 0) { 
       synchronized (this) { 
        // Block until someone enters the next desired value 
        wait(); 
       } 
      } 

      _list.add(item); 
      _values.poll(); 

      synchronized (this) { 
       notifyAll(); 
      } 
     } 
    } 
} 

我的問題是任何人可以幫助我找到的線程錯誤?

謝謝!

+4

如果你知道到底有多少價值爲什麼不分配適當大小的數組,並讓每個線程將值寫入正確的點?沒有阻塞,沒有比賽條件... – tzaman 2010-05-01 00:28:17

+1

啊...嗯,是的,這將是一個更好的解決方案。呃,當你疲倦的時候,你有沒有編碼的原因。感謝您的建議,我會執行它並查看它的執行情況。 – mgbowen 2010-05-01 00:34:22

+1

您的解決方案做得很好,而且性能更快!非常感謝你! – mgbowen 2010-05-01 00:41:22

回答

5

您要添加的線程來提高性能,但這樣做引入了性能瓶頸。

你寫:

我想,每一個訂購點爲計算將工作最好

我們來評估這樣的性能開銷。

將單個點添加到已排序的列表可以非常快地完成。

使用BubbleSort算法,這將需要O(N)時間 - 線性時間與已經存在的列表的大小成正比。重複這一點你添加的每一點意味着你重複這個O(N)操作N次,給出O(N^2)的表現。在很多內核上運行這可能會縮短掛鐘時間,但殺手級別是N^2性能。

不幸的是,Java並沒有在內部使用BubbleSort算法 - 它使用QuickSort,它通常表現更好。爲了將單個數字添加到已排序的列表中,QuickSort需要O(N lg N)個時間;重複這N次得到O(N^2 lg N)的性能。雙ouch。

訴諸列表很多次是低效的 - 如果你不是僅僅收集了所有的物品放在一起,整理他們一旦末,排序只需花你O(N LG N)的時候,給你一個更好的結束結果。

請注意,具有多個核心的結果只是以一個常數因子進行縮放,並不會改變整體分析。多線程技術可以顯着提高性能,所以您採用正確的方式,但始終要測量結果。

在檢查來自這個績效考覈的結果,我發現HashMap的是一個性能瓶頸,因爲我這樣做,同步在一個線程

而不是使用一個HashMap的,請嘗試使用一個簡單的列表將點聚集在一起 - 只要您提前將列表容量設置得足夠高,向列表添加點幾乎是恆定的,接近零的時間。

+0

事實上,使用數組效果好得多,因爲我已經知道我會得到多少點數,以及它們的順序。除非tzaman想要把他作爲答案,否則我會將其標記爲答案。謝謝! – mgbowen 2010-05-01 00:48:46

0

如果你把同步方法放在整個BlockingList.put方法的前面,它會工作嗎?如果

線程A執行

while (item.realX.compareTo(_values.peek()) != 0) { 

線程B的推移和不

_list.add(item); 
_values.poll(); 

synchronized (this) { 
    notifyAll(); 
} 

線程A確實

synchronized (this) { 
    // Block until someone enters the next desired value 
    wait(); 
} 
+0

不幸的是,不過,看到關於這個問題的評論,我用tzaman的解決方案解決了這個問題,這比我的錯誤要好得多。 – mgbowen 2010-05-01 00:42:22

+0

更新了答案。你怎麼看? (即使你更新到另一個解決方案,競爭條件問題仍然是一個相關的問題) – aioobe 2010-05-01 00:43:41

+0

是的,它似乎是競爭條件的地方,在線程A可以調用wait之前線程B調用notifyAll() ()'。我想讓它「等待(10)」或其他任意數字會更好,這樣它會稍後檢查。 – mgbowen 2010-05-01 00:47:10

2

你必須認識到爲什麼Java需要wait()和notify()來同步。

想象一下,你是一個線程,並剛剛完成此任務。

while (item.realX.compareTo(_values.peek()) != 0) { 
      synchronized (this) { 
       // Block until someone enters the next desired value 
       wait(); 
      } 
     } 

你有鎖,釋放它的等待,然後拿回來時,等待結束。 然後 - 你又放棄了!

 _list.add(item); 
     _values.poll(); 

在此期間,您的其他線程可以訪問該鎖。 然後你繼續並重新獲得一個鎖,以便輪詢列表並執行實際的「放入」。

 synchronized (this) { 
      notifyAll(); 
     } 

的「同步​​」的關鍵是使你能保持鎖,而你所做的更改 - 你應該把相同「中的「等待」,「添加」和「民意調查」報告同步'聲明。

0

瞭解線程競爭條件以及如何解決這些問題,你應該閱讀這本書的最好方法:

的Java併發實踐(2006)