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

相关问题