我不明白为什么在迭代multimap
时遇到ConcurrentModificationException。
我读了以下entry,但我不确定我是否理解了整个事情。
我试图添加一个synchronized块。但我怀疑的是与什么同步,何时。
multimap
是一个字段,创建如下:
private Multimap<GenericEvent, Command> eventMultiMap =
Multimaps.synchronizedMultimap(HashMultimap.<GenericEvent, Command> create());
并像这样使用:
eventMultiMap.put(event, command);
并且像这样(我试图在地图上同步这个部分,但没有成功)
for (Entry<GenericEvent, Command> entry : eventMultiMap.entries()) {
if (entry.getValue().equals(command)) {
eventMultiMap.remove(entry.getKey(), entry.getValue());
nbRemoved++;
}
}
答案 0 :(得分:11)
在迭代它时对集合调用remove将导致每次都出现ConcurrentModificationException,即使它们都在同一个线程中完成 - 要做的正确事情是获取一个显式迭代器并调用.remove()
编辑:修改您的示例:
Iterator<Map.Entry<GenericEvent, Command>> i = eventMultiMap.entries().iterator();
while (i.hasNext()) {
if (i.next().getValue().equals(command)) {
i.remove();
nbRemoved++;
}
}
答案 1 :(得分:4)
如果另一个线程在此逻辑运行时可以修改多重映射,则需要将同步块添加到MHarris的代码中:
synchronized (eventMultimap) {
Iterator<Entry<GenericEvent, Command>> i = eventMultiMap.entries.iterator();
while (i.hasNext()) {
if (i.next().getValue().equals(command)) {
i.remove();
nbRemoved++;
}
}
}
或者,您可以省略迭代器,如下所示,
synchronized (eventMultimap) {
int oldSize = eventMultimap.size();
eventMultimap.values().removeAll(Collections.singleton(command));
nbRemoved = oldSize - eventMultimap.size();
}
removeAll()调用不需要同步。但是,如果省略synchronized块,则multimap可能会在removeAll()调用和其中一个size()调用之间发生变化,从而导致nbRemoved值不正确。
现在,如果您的代码是单线程的,并且您只是想避免ConcurrentModificationException调用,则可以省略Multimaps.synchronizedMultimap和synchronized(eventMultimap)逻辑。
答案 2 :(得分:4)
您可能希望看到this blogpost在遍历多图时产生ConcurrentModificationException
的另一个陷阱,没有其他线程干扰。简而言之,如果您遍历multimap的键,访问与每个键关联的相应值集合并从这样的集合中删除一些元素,如果该元素恰好是集合的最后一个,那么您将要当您尝试访问下一个键时有ConcurrentModificationException
- 因为清空一个集合会触发删除键,从而在结构上修改多图的键集。
答案 3 :(得分:2)
在java8中,您还可以使用lambda方法:
eventMultiMap.entries().removeIf(genericEventCommandEntry -> genericEventCommandEntry.getValue().equals(command));
答案 4 :(得分:1)
如果您不关心密钥,我更喜欢Multimap.values().iterator()
。您还应该尝试尽可能远离使用synchronized块,因为您无法有效地优先读取/写入。
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock writeLock = lock.writeLock();
public void removeCommands(Command value) {
try {
writeLock.lock();
for (Iterator<Command> it = multiMap.values().iterator(); it.hasNext();) {
if (it.next() == value) {
it.remove();
}
}
} finally {
writeLock.unlock();
}
}