5

代碼:如何在併發線程中操作values()和put()時避免HashMap「ConcurrentModificationException」?

我有一個HashMap

private Map<K, V> map = new HashMap<>(); 

一種方法將把ķ-V對進入它通過調用put(K,V)

另一種方法想從它的值提取一組隨機元素:

int size = map.size(); // size > 0 
V[] value_array = map.values().toArray(new V[size]); 
Random rand = new Random(); 
int start = rand.nextInt(size); int end = rand.nextInt(size); 
// return value_array[start .. end - 1] 

的兩種方法稱爲在兩個不同的併發線程


錯誤:

我有一個ConcurrentModificationException錯誤:

at java.util.HashMap$HashIterator.nextEntry(Unknown Source) 
at java.util.HashMap$ValueIterator.next(Unknown Source) 
at java.util.AbstractCollection.toArray(Unknown Source) 

看來,在一個線程中toArray()方法實際上是遍歷HashMap和其他線程put()修改發生。

Question: How to avoid "ConcurrentModificationException" while using HashMap.values().toArray() and HashMap.put() in concurrent threads?
Directly avoiding using values().toArray() in the second method is also OK.

+1

執行訪問了'map'在同步塊中的代碼:'同步(圖){...}'' – Titus 2014-10-29 02:38:40

+1

同步(圖){...}'應該工作(如果你處處應用它)。 Collections.synchronizedMap不起作用。請參閱http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedMap%28java.util.Map%29 – Thilo 2014-10-29 03:15:06

回答

4

您需要提供一些同步的水平,使得調用puttoArray調用執行受阻,反之亦然。有兩個簡單的方法:

  1. 包裝你撥打puttoArray在​​塊,同樣的鎖定對象(這可能是地圖本身或者其它對象)上同步。
  2. 使用Collections.synchronizedMap()

    private Map<K, V> map = Collections.synchronizedMap(new HashMap<>()); 
    

  3. 使用ConcurrentHashMap代替HashMap把你的地圖變成同步地圖。

編輯:使用Collections.synchronizedMap的問題是,一旦調用values()回報,併發保護就會消失。此時,呼叫put()toArray()可能會同時執行。 A ConcurrentHashMap有一個類似的問題,但它仍然可以使用。從文檔的ConcurrentHashMap.values()

The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException , and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+0

@Thilo - 正確。三種方法。 :) – 2014-10-29 02:43:57

+0

@Thilo謝謝。但是,我已經閱讀了一些說明,ConcurrentHashMap不一定能解決ConcurrentModificationException(但現在無法找到源代碼)。爲什麼它在這個/我的情況下工作? – hengxin 2014-10-29 02:49:05

+1

你需要'values()'來工作多線程,Javadoc說:「視圖的迭代器是一個」弱一致「的迭代器,永遠不會拋出ConcurrentModificationException,並且保證遍歷構造迭代器時存在的元素,以及可能(但不能保證)反映施工後的任何改動。「 – Thilo 2014-10-29 02:52:05

0

我會用ConcurrentHashMap的,而不是一個HashMap並保護其免受由不同的線程併發讀取和修改。請參閱下面的實現。線程1和線程2不可能同時讀寫。當線程1將Map中的值提取到數組時,調用storeInMap(K,V)的所有其他線程將掛起並等待地圖,直到第一個線程完成對象。

注意:在此上下文中我不使用同步方法;我並不完全排除同步方法,但我會謹慎使用它。同步方法實際上只是語法糖,用於獲取'this'上的鎖並在方法持續期間保持它,以便它可能會損害吞吐量。

private Map<K, V> map = new ConcurrentHashMap<K, V>(); 

// thread 1 
public V[] pickRandom() { 
    int size = map.size(); // size > 0 
    synchronized(map) { 
     V[] value_array = map.values().toArray(new V[size]); 
    } 
    Random rand = new Random(); 
    int start = rand.nextInt(size); 
    int end = rand.nextInt(size); 
    return value_array[start .. end - 1] 
} 

// thread 2 
public void storeInMap(K, V) { 
    synchronized(map) { 
     map.put(K,V); 
    } 
} 
+1

爲什麼'syncronized'塊如果你已經使用'ConcurrentHashMap'? – 2015-07-14 19:48:45

相關問題