2015-02-09 48 views
3

我在同一行代碼中看到多個線程死鎖的問題。 我無法在本地或任何測試中重現此問題,但「生產線程轉儲」已清楚顯示問題。Java在本地資源上同步期間死鎖?

我不明白爲什麼線程會在下面的同步線上被阻塞,因爲在調用堆棧或任何其他線程中的對象沒有其他同步。有沒有人知道發生了什麼,或者我怎麼能夠重現這個問題(目前試圖使用15個線程在一個循環中全部擊中trim(),同時通過我的隊列處理2000個任務 - 但無法重現)

在下面的線程轉儲中,我認爲具有「鎖定」狀態的多個線程可能是Java Bug的體現:http://bugs.java.com/view_bug.do?bug_id=8047816,其中JStack報告線程處於錯誤狀態。 (我正在使用JDK版本:1.7.0_51)

乾杯!

這裏是線程轉儲線程的景色.....

"xxx>Job Read-3" daemon prio=10 tid=0x00002aca001a6800 nid=0x6a3b waiting for monitor entry [0x0000000052ec4000] 
    java.lang.Thread.State: BLOCKED (on object monitor) 
    at com.mycompany.collections.CustomQueue.remove(CustomQueue.java:101) 
    - locked <0x00002aae6465a650> (a java.util.ArrayDeque) 
    at com.mycompany.collections.CustomQueue.trim(CustomQueue.java:318) 
    at com.mycompany.collections.CustomQueue.itemProcessed(CustomQueue.java:302) 
    at com.mycompany.collections.CustomQueue.trackCompleted(CustomQueue.java:147) 
    at java.util.concurrent.ThreadPoolExecutor.afterExecute(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 
    at java.lang.Thread.run(Unknown Source) 

    Locked ownable synchronizers: 
    - <0x00002aaf5f9c2680> (a java.util.concurrent.ThreadPoolExecutor$Worker) 

"xxx>Job Read-2" daemon prio=10 tid=0x00002aca001a5000 nid=0x6a3a waiting for monitor entry [0x0000000052d83000] 
    java.lang.Thread.State: BLOCKED (on object monitor) 
    at com.mycompany.collections.CustomQueue.remove(CustomQueue.java:101) 
    - locked <0x00002aae6465a650> (a java.util.ArrayDeque) 
    at com.mycompany.collections.CustomQueue.trim(CustomQueue.java:318) 
    at com.mycompany.collections.CustomQueue.itemProcessed(CustomQueue.java:302) 
    at com.mycompany.collections.CustomQueue.trackCompleted(CustomQueue.java:147) 
    at java.util.concurrent.ThreadPoolExecutor.afterExecute(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 
    at java.lang.Thread.run(Unknown Source) 

    Locked ownable synchronizers: 
    - <0x00002aaf5f9ed518> (a java.util.concurrent.ThreadPoolExecutor$Worker) 

"xxx>Job Read-1" daemon prio=10 tid=0x00002aca00183000 nid=0x6a39 waiting for monitor entry [0x0000000052c42000] 
    java.lang.Thread.State: BLOCKED (on object monitor) 
    at com.mycompany.collections.CustomQueue.remove(CustomQueue.java:101) 
    - waiting to lock <0x00002aae6465a650> (a java.util.ArrayDeque) 
    at com.mycompany.collections.CustomQueue.trim(CustomQueue.java:318) 
    at com.mycompany.collections.CustomQueue.itemProcessed(CustomQueue.java:302) 
    at com.mycompany.collections.CustomQueue.trackCompleted(CustomQueue.java:147) 
    at java.util.concurrent.ThreadPoolExecutor.afterExecute(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 
    at java.lang.Thread.run(Unknown Source) 

    Locked ownable synchronizers: 
    - <0x00002aaf5f9ecde8> (a java.util.concurrent.ThreadPoolExecutor$Worker) 


"xxx>Job Read-0" daemon prio=10 tid=0x0000000006a83000 nid=0x6a36 waiting for monitor entry [0x000000005287f000] 
    java.lang.Thread.State: BLOCKED (on object monitor) 
     at com.mycompany.collections.CustomQueue.remove(CustomQueue.java:101) 
    - waiting to lock <0x00002aae6465a650> (a java.util.ArrayDeque) 
    at com.mycompany.collections.CustomQueue.trim(CustomQueue.java:318) 
    at com.mycompany.collections.CustomQueue.itemProcessed(CustomQueue.java:302) 
    at com.mycompany.collections.CustomQueue.trackCompleted(CustomQueue.java:147) 
    at java.util.concurrent.ThreadPoolExecutor.afterExecute(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 
    at java.lang.Thread.run(Unknown Source) 

這裏是Java代碼中提取,這顯示了錯誤...

public class Deadlock { 
     final Deque<Object> delegate = new ArrayDeque<>(); 
     final long maxSize = Long.MAX_VALUE; 

     private final AtomicLong totalExec = new AtomicLong(); 
     private final Map<Object, AtomicLong> totals = new HashMap<>(); 
     private final Map<Object, Deque<Long>> execTimes = new HashMap<>(); 

     public void trim() { 
      //Possible optimization is evicting in chunks, segmenting by arrival time 
      while (this.totalExec.longValue() > this.maxSize) { 
       final Object t = this.delegate.peek(); 
       final Deque<Long> execTime = this.execTimes.get(t); 
       final Long exec = execTime.peek(); 
       if (exec != null && this.totalExec.longValue() - exec > this.maxSize) { 
        //If Job Started Inside of Window, remove and re-loop 
        remove(); 
       } 
       else { 
        //Otherwise exit the loop 
        break; 
       } 
      } 
     } 

     public Object remove() { 
      Object removed; 
      synchronized (this.delegate) { //4 Threads deadlocking on this line ! 
       removed = this.delegate.pollFirst(); 
      } 
      if (removed != null) { 
       itemRemoved(removed); 
      } 
      return removed; 
     } 

     public void itemRemoved(final Object t) { 
      //Decrement Total & Queue 
      final AtomicLong catTotal = this.totals.get(t); 
      if (catTotal != null) { 
       if (!this.execTimes.get(t).isEmpty()) { 
        final Long exec = this.execTimes.get(t).pollFirst(); 
        if (exec != null) { 
         catTotal.addAndGet(-exec); 
         this.totalExec.addAndGet(-exec); 
        } 
       } 
      } 
     } 
    } 
+0

當您在等待鎖定第二個鎖時尚未鎖定時,它不是死鎖。哪個線程當前持有有爭議的鎖(0x000000005287f000)? – Thilo 2015-02-09 14:59:45

+1

這是很多'Deque's你有;你爲什麼不把「Object」和它的執行時間捆綁到同一個類中?這將大大簡化你的代碼(imho)。另外,爲什麼不使用線程安全的'BlockingQueue'呢? – fge 2015-02-09 15:03:51

+0

只需要注意,「this.where」無處不在。 – vikingsteve 2015-02-09 15:05:30

回答

0

感謝這裏的回覆,很明顯問題是沒有線程安全地使用多個集合。

要解決這個問題,我做了修剪方法同步,取而代之的HashMap的使用與ConcurrentHashMap的和ArrayDeque與LinkedBlockingDeque (併發集合FTW!)

進一步增強計劃是改變2使用將Maps分離爲包含自定義對象的單個Map,從而使操作(在itemRemoved中)保持原子。

5

documentation for HashMap

請注意,此實現不同步。如果多個線程 同時訪問哈希映射,並且至少有一個線程在結構上修改了映射,它必須在外部同步。

(強調他們)

您在閱讀和以非同步的方式從Map在寫到/。

我沒有理由認爲你的代碼是線程安全的。

我建議你在trim處有一個無限循環,這是由於缺少線程安全性。

輸入一個同步塊比較慢,所以線程轉儲可能總是顯示至少有幾個線程正在等待獲取鎖。

+0

這隻有在修改後才成立。可能會有修改,但我們無法根據代碼來判斷。 – 2015-02-09 15:07:31

+2

@JohnVint代碼允許修改。如果發生修改,代碼變得不可預知。此代碼不是線程安全的 - 進一步分析沒有意義。 – 2015-02-09 15:09:52

+0

如果OP選擇使用'HashMap',它永遠不會因爲代碼的設計而改變,否則絕對沒有錯。在非變異HashMap上不必要地「同步」是錯誤的。 – 2015-02-09 15:12:47

1

您的第一個線程在等待pollFirst時持有鎖。

"xxx>Job Read-3" daemon prio=10 tid=0x00002aca001a6800 nid=0x6a3b waiting for monitor entry [0x0000000052ec4000] 
    java.lang.Thread.State: BLOCKED (on object monitor) 
    at com.mycompany.collections.CustomQueue.remove(CustomQueue.java:101) 
    - locked <0x00002aae6465a650> (a java.util.ArrayDeque) 
    at com.mycompany.collections.CustomQueue.trim(CustomQueue.java:318) 

其他線程正在等待獲取鎖。 您需要提供整個線程轉儲以確定哪個線程持有0x0000000052ec4000上的鎖,這是阻止您的pollFirst調用返回的原因。

1

爲了解決死鎖問題,您至少需要在同一個線程中同時鎖定至少兩個對象,這是您發佈的代碼似乎沒有做到的事情。您指向的錯誤可能會適用,但在我閱讀它時,這是一個整容問題,並且線程未被鎖定,而是等待獲取有關對象(ArrayDeque)的鎖定。如果遇到死鎖,您應該在日誌中看到「死鎖」消息。它會調出兩個互相阻塞的線程。

我不相信線程轉儲表示存在死鎖。它只是告訴你在轉儲的那一刻有多少線程在顯示器上等待。由於在特定時刻只有一個線程可能擁有顯示器,因此不應該感到驚訝。

你在你的應用程序中看到了什麼行爲,導致你相信你會陷入僵局?代碼中有很多遺漏,特別是委託Dequeue中的對象來自何處。我的猜測是你沒有徹底的僵局,但其他一些問題可能看起來像是僵局。