如何在并发线程中操作`values()`和`put()`时避免使用HashMap“ConcurrentModificationException”?
码:
我有一个HashMap
private Map map = new HashMap();
一种方法是通过调用put(K,V)
将KV对放入其中。
另一种方法想从其值中提取一组随机元素:
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()
修改。
问题:如何在并发线程中使用HashMap.values()。toArray()和HashMap.put()时避免“ConcurrentModificationException”?
直接避免在第二种方法中使用values().toArray()
也行。
您需要提供某种级别的同步,以便在执行toArray
调用时阻止对put
的调用,反之亦然。 有两种简单的方法:
- 将对
put
和toArray
调用包装在synchronized
块中,这些块在同一个锁对象(可能是映射本身或其他对象)上进行同步。 -
使用
Collections.synchronizedMap()
将地图转换为同步地图private Map
map = Collections.synchronizedMap(new HashMap<>()); -
使用
ConcurrentHashMap
而不是HashMap
。
编辑:使用Collections.synchronizedMap
的问题是,一旦调用values()
返回,并发保护将消失。 此时,对put()
和toArray()
调用可能会同时执行。 ConcurrentHashMap
有一些类似的问题,但它仍然可以使用。 从ConcurrentHashMap.values()
的文档:
视图的迭代器是一个“弱一致”的迭代器,它永远不会抛出
ConcurrentModificationException
,并保证遍历构造迭代器时存在的元素,并且可能(但不保证)反映构造之后的任何修改。
我会使用ConcurrentHashMap而不是HashMap,并保护它免受不同线程的并发读取和修改。 请参阅以下实施。 线程1和线程2不可能同时读写。 当线程1将值从Map提取到数组时,调用storeInMap(K,V)的所有其他线程将挂起并在地图上等待,直到第一个线程完成对象。
注意:我不在此上下文中使用synchronized方法; 我不完全排除同步方法,但我会谨慎使用它。 同步方法实际上只是用于获取“this”锁定并在方法持续时间内保持锁定的语法糖,因此它可能会损害吞吐量。
private Map map = new ConcurrentHashMap(); // 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); } }