Guava MultiMap和ConcurrentModificationException

时间:2009-10-15 12:53:05

标签: java concurrency guava multimap concurrentmodification

我不明白为什么在迭代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++;
    }
}

5 个答案:

答案 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();
  }
}