2013-12-10 58 views
1

我正在處理來自TCP套接字(每秒10套)的很多事件,所以我使用多線程來處理這些事件。ConcurrentModificationException在同步方法

public class MainActivity extends Activity { 
    ... 

    // In this Map I store the tab name and the associated TabHost.TabSpec instance 
    private static Map<String, TabHost.TabSpec> Tabs = Collections.synchronizedMap(new LinkedHashMap<String, TabHost.TabSpec>()); 
    // In this Map I store pairs of tab-names and a HashSet of undelivered messages 
    // It's a class that extends Map<String, HashSet<String>> with some additional functions that doesn't have anything to do with Tabs. 
    private static NamesListing Names = Collections.synchronizedMap(new LinkedHashMap<String, HashSet<String>>()); 

    // Yes, I know the names don't follow the Java standards, but I keeped them for mantaining the question coherence. I will change it in my code, though. 

    synchronized private static void ProcessEvent(final String name, final String message) { 
    // Low-priority thread 
    final Thread lowp = new Thread(
     new Runnable() { 
     public void run() { 
      final Iterator<String> iter = Tabs.keySet().iterator(); 
      while (iter.hasNext()) { 
      final String tabname = iter.next(); 
      // This just returns an int making some calculations over the tabname 
      final int Status = Names.getUserStatus(tabname, message); 

      // Same than getUserStatus 
      if ((Names.isUserSpecial(Status)) && (name.equals(tabname))) { 
       // This just removes a line from the HashSet 
       Names.delLine(tabname, message); 
      } 
      } 
     } 
     }); 
    lowp.setPriority(3); 
    lowp.start(); 
    } 

    ... 
} 

多數時候這個作品的權利,但有時也有這些事件的一些雪崩,並somethimes我得到一個ConcurrentModificationException的:

12-10 14:08:42.071:E/AndroidRuntime( 28135):FATAL EXCEPTION: 線程-369 12-10 14:08:42.071:E/AndroidRuntime(28135): java.util.ConcurrentModificationException 12-10 14:08:42.071: E/AndroidRuntime(28135):at java.util.LinkedHashMap $ LinkedHashIterator.nextEntry(LinkedHashMap.java:347) 12-10 14:08:42.071: E/AndroidRuntime(28135):at java.util.LinkedHashMap $ KeyIterator.next(LinkedHashMap.java:367)12-10 14:08:42.071:E/AndroidRuntime(28135):at es.irchispano.chat。 MainActivity $ 6.run(MainActivity.java:244)12-10 14:08:42.071:E/AndroidRuntime(28135):在 java.lang.Thread.run(Thread.java:841)

注:244行對應於

final String tabname = iter.next(); 

聲明。

對我來說這似乎很奇怪,因爲我正在使用Collections.synchronizedMap,並且處理這些行的方法是同步的,那爲什麼它仍然在發生?

謝謝!

---------- ----------編輯

很抱歉的簡潔初始代碼;我試圖儘可能簡化,但顯然這不是一個好主意。我正在粘貼實際的代碼。當然,這些結構中的每一個都被初始化了(否則我不會有問題:-)),現在我將閱讀你所有的良心評論,我會發布我會發現的。謝謝大家的支持!

+3

'doSomeAdditionalStuff()'修改地圖嗎? – rgettman

+0

^因爲這是關鍵:) –

+0

@rgettman不會做任何事情的地圖 – nKn

回答

1

您已經向我們展示了地圖Tabs是如何創建的(Java命名約定會指定其名稱以小寫字母開頭),但不會顯示它是如何填充的。實際上,地圖總是空的,while循環會運行零次。此外,本地變量tabname未使用,但可能在您的實際代碼中未使用。

也就是說,看起來ProcessEvent將爲每個事件運行一次。它是靜態的,並且是同步的,這意味着它將獲得MainActivity.class,的監視器,並且同一對象上沒有其他同步方法可以同時運行。

但是,它啓動一個新的線程,它執行實際的工作,並立即返回。該線程不同步,所以任何數量的這些工作線程可以同時運行,全部使用相同的映射。該地圖用Collections.synchronizedMap包裝,這是防止併發修改的唯一保護措施。

這樣一個同步映射將不允許多個調用者同時調用它的方法,但是對不同方法的單獨調用可以任意交錯。例如,當一個呼叫者正在向地圖中添加新條目時,其他呼叫者無法訪問地圖。但是,一個調用者可能從map中獲取密鑰集,從密鑰集中獲取迭代器,並開始迭代它,然後在另一個線程中的另一個調用者添加,修改或刪除條目,並且最後,第一個線程繼續在按鍵迭代,並獲得ConcurrentModificationException.

我會用java.util.concurrent.ConcurrentHashMap,而不是建議,並從ProcessEvent.

+1

由於這個完整的說明,我可以做一些額外的調試,並發現這個異常正好發生在處理另一種事件時,ADDS a新標籤在背景中。因此,我會在本主題中記住所有有價值的迴應,並進行必要的更改。我已經提出了所有有用的評論,但這個幫助我最多,所以我接受它。多謝你們! – nKn

1

本示例中的synchronized關鍵字在MainActivity類中獲取鎖定,然後該方法啓動一個新線程並立即釋放鎖定。

無法保證在處理新請求之前第一個事件的迭代已結束。

新的請求可能會導致兩個線程同時在地圖上迭代。

如果在方法doSomeAdditionalStuff()中存在對映射的修改操作,這將導致一個線程修改映射,而另一個線程仍在迭代整個映射,導致ConcurrentModificationException。

1

訪問移除synchronized關鍵字來Tabs不受任何鎖的保護。可以訪問Tabsrunnable.run()也不受任何鎖保護。你的代碼可以並行產生多個線程。因爲每個線程的runnable都可以訪問你的地圖(Tabs),所以這些線程中的一個可能會修改那個地圖,而另一個線程正在迭代它。這將導致您看到的ConcurrentModificationException

1

僅使用Collections.synchronizedMap意味着多個線程可以同時在其上調用get/put/delete。它不會防止在更改而Iterator迭代它時發生的錯誤。這甚至可以在一個單獨的線程發生,例如下面的線程應該把它是否有(我認爲)至少3個元素的地圖:

final Iterator<String> iter = Tabs.keySet().iterator(); 
iter.next(); 
String k = iter.next(); 
final Iterator<String> iter2 = Tabs.keySet().iterator(); 
iter2.next(); 
Tabs.delete(k); 
iter2.next(); 

現在,正如其他人指出,多線程可同時迭代Map,因爲run方法不同步。但是,如果Tabs未被修改,那麼這不是錯誤的來源。

您沒有顯示在哪裏Tabs正在修改。 這是因爲在迭代器遍歷導致異常的迭代器時,映射正在被修改。

解決方法是遍歷它並使用相同的鎖修改它。