Java 在并发线程中操作`values()`和`put()`时如何避免HashMap“ConcurrentModificationException”?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26621907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to avoid HashMap "ConcurrentModificationException" while manipulating `values()` and `put()` in concurrent threads?
提问by hengxin
Code:
代码:
I have a HashMap
我有一个哈希映射
private Map<K, V> map = new HashMap<>();
One method will put K-V pair into it by calling put(K,V)
.
一种方法是通过调用将 KV 对放入其中put(K,V)
。
The other method wants to extract a set of random elements from its values:
另一种方法想从其值中提取一组随机元素:
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]
The two methods are called in two different concurrent threads.
这两个方法在两个不同的并发线程中调用。
Error:
错误:
I got a ConcurrentModificationException
error:
我有一个ConcurrentModificationException
错误:
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$ValueIterator.next(Unknown Source)
at java.util.AbstractCollection.toArray(Unknown Source)
It seems that the toArray()
method in one thread is actually iterating over the HashMap and a put()
modification in other thread occurs.
似乎toArray()
一个线程中的方法实际上是在 HashMap 上迭代,而put()
在另一个线程中发生了修改。
Question:How to avoid "ConcurrentModificationException" while using HashMap.values().toArray() and HashMap.put() in concurrent threads?
Directly avoiding usingvalues().toArray()
in the second method is also OK.
问题:如何在并发线程中使用 HashMap.values().toArray() 和 HashMap.put() 时避免“ConcurrentModificationException”?
直接避免values().toArray()
在第二种方法中使用也是可以的。
采纳答案by Ted Hopp
You need to provide some level of synchronization so that the call to put
is blocked while the toArray
call is executing and vice versa. There are threetwo simple approaches:
您需要提供某种程度的同步,以便put
在toArray
调用执行时阻塞调用,反之亦然。有3点两个简单的方法:
- Wrap your calls to
put
andtoArray
insynchronized
blocks that synchronize on the same lock object (which might be the map itself or some other object). Turn your map into a synchronized map using
Collections.synchronizedMap()
private Map<K, V> map = Collections.synchronizedMap(new HashMap<>());
Use a
ConcurrentHashMap
instead of aHashMap
.
- 包装你的来电
put
,并toArray
以synchronized
块,同样的锁定对象(这可能是地图本身或者其它对象)上同步。 使用
Collections.synchronizedMap()
private Map<K, V> map = Collections.synchronizedMap(new HashMap<>());
使用 a
ConcurrentHashMap
代替 aHashMap
。
EDIT: The problem with using Collections.synchronizedMap
is that once the call to values()
returns, the concurrency protection will disappear. At that point, calls to put()
and toArray()
might execute concurrently. A ConcurrentHashMap
has a somewhat similar problem, but it can still be used. From the docs for ConcurrentHashMap.values()
:
编辑:使用的问题Collections.synchronizedMap
是一旦调用values()
返回,并发保护就会消失。此时,调用put()
和toArray()
可能会并发执行。AConcurrentHashMap
有一些类似的问题,但它仍然可以使用。从文档中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.
视图的迭代器是一个“弱一致性”迭代器,它永远不会 throw
ConcurrentModificationException
,并保证遍历在构造迭代器时存在的元素,并且可能(但不保证)反映构造之后的任何修改。
回答by Danny Ho
I would use ConcurrentHashMap instead of a HashMap and protect it from concurrent reading and modification by different threads. See the below implementation. It is not possible for thread 1 and thread 2 to read and write at the same time. When thread 1 is extracting values from Map to an array, all other threads that invoke storeInMap(K, V) will suspend and wait on the map until the first thread is done with the object.
我会使用 ConcurrentHashMap 而不是 HashMap 并保护它免受不同线程的并发读取和修改。请参阅下面的实现。线程 1 和线程 2 不可能同时读写。当线程 1 将值从 Map 提取到数组时,调用 storeInMap(K, V) 的所有其他线程将挂起并在映射上等待,直到第一个线程处理完对象。
Note: I do not use synchronized method in this context; I do not completely rule out synchronized method but I would use it with caution. A synchronized method is actually just syntax sugar for getting the lock on 'this' and holding it for the duration of the method so it can hurt throughput.
注意:我在这个上下文中不使用同步方法;我不完全排除同步方法,但我会谨慎使用它。同步方法实际上只是一种语法糖,用于获取“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);
}
}