10

我不明白为什么当我遍历 this 时会得到 ConcurrentModificationException multimap。我阅读了以下条目,但我不确定我是否理解了整个事情。我试图添加一个同步块。但我的疑问是与什么同步,何时同步。

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++;
    }
}
4

5 回答 5

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++;
    }
}
于 2009-10-15T12:56:02.320 回答
5

您可能希望看到这篇博文中的另一个陷阱,即ConcurrentModificationException在遍历多图时会产生一个陷阱,而没有其他线程干扰。简而言之,如果您遍历多图的键,访问与每个键关联的值的相应集合并从这样的集合中删除一些元素,如果该元素恰好是ConcurrentModificationException您尝试访问时将拥有的集合的最后一个下一个键 - 因为清空集合会触发键的删除,从而在结构上修改多图的键集。

于 2010-01-23T00:50:00.413 回答
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() 调用不需要同步。但是,如果省略同步块,多重映射可能会在 removeAll() 调用和其中一个 size() 调用之间发生变化,从而导致 nbRemoved 的值不正确。

现在,如果您的代码是单线程的,并且您只想避免 ConcurrentModificationException 调用,则可以省略 Multimaps.synchronizedMultimap 和 synchronized (eventMultimap) 逻辑。

于 2009-11-04T03:37:43.560 回答
1

Multimap.values().iterator()如果您不关心密钥,我更喜欢。你还应该尽量避免使用同步块,因为你不能有效地优先考虑读/写。

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();
  }
}
于 2013-12-14T01:18:28.870 回答
1

在 java8 中,您还可以使用 lambda 方法:

eventMultiMap.entries().removeIf(genericEventCommandEntry -> genericEventCommandEntry.getValue().equals(command));

于 2017-05-11T18:31:39.910 回答