Guava MultiMap和ConcurrentModificationException [重复]

问题描述 投票:9回答:5

我不明白为什么当我遍历这个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++;
    }
}
java concurrency guava multimap concurrentmodification
5个回答
2
投票

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

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


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

5
投票

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


4
投票

如果另一个线程在此逻辑运行时可以修改多重映射,则需要向MHarris的代码添加一个synchronized块:

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)逻辑。


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();
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.