如何在并发线程中操作`values()`和`put()`时避免使用HashMap"ConcurrentModificationException"?

hen*_*xin 6 java concurrency multithreading hashmap concurrentmodification

码:

我有一个HashMap

private Map<K, V> map = new HashMap<>();
Run Code Online (Sandbox Code Playgroud)

一种方法是通过调用将KV对放入其中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]
Run Code Online (Sandbox Code Playgroud)

这两个方法在两个不同的并发线程中调用.


错误:

我收到一个ConcurrentModificationException错误:

at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$ValueIterator.next(Unknown Source)
at java.util.AbstractCollection.toArray(Unknown Source)
Run Code Online (Sandbox Code Playgroud)

似乎toArray()一个线程中的方法实际上是在HashMap上迭代,并且put()在其他线程中发生了修改.

问题:如何在并发线程中使用HashMap.values().toArray()和HashMap.put()时避免"ConcurrentModificationException"?
直接避免values().toArray()在第二种方法中使用也行.

Ted*_*opp 5

您需要提供某种程度的同步,以便在执行呼叫时put阻止对的toArray调用,反之亦然。有三种简单的方法:

  1. 包装你的来电put,并toArraysynchronized块,同样的锁定对象(这可能是地图本身或者其它对象)上同步。
  2. 使用以下工具将地图转换为同步地图 Collections.synchronizedMap()

    private Map<K, V> map = Collections.synchronizedMap(new HashMap<>());
    
    Run Code Online (Sandbox Code Playgroud)

  3. 使用ConcurrentHashMap而不是HashMap

编辑:使用的问题Collections.synchronizedMap是一旦调用values()返回,并发保护将消失。此时,对put()和的调用toArray()可能会同时执行。A ConcurrentHashMap也有类似的问题,但仍然可以使用。从文档中获取ConcurrentHashMap.values()

视图的迭代器是一个“弱一致”的迭代器,它永远不会抛出ConcurrentModificationException,并保证遍历迭代器构造时存在的元素,并且可以(但不保证)反映构造之后的任何修改。