【问题标题】:Guava MultiMap and ConcurrentModificationException [duplicate]Guava MultiMap 和 ConcurrentModificationException [重复]
【发布时间】:2010-12-07 00:41:56
【问题描述】:

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

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


【解决方案1】:

在迭代时对集合调用 remove 每次都会导致 ConcurrentModificationException,即使这一切都在同一个线程中完成 - 正确的做法是获取一个显式迭代器并在其上调用 .remove() .

编辑:修改您的示例:

Iterator<Map.Entry<GenericEvent, Command>> i = eventMultiMap.entries().iterator();
while (i.hasNext()) {
    if (i.next().getValue().equals(command)) {
        i.remove();
        nbRemoved++;
    }
}

【讨论】:

  • 好的,我明白了。但是 Iterator 只能接受一种参数类型。不 ?如果我迭代我的地图的值,并删除它们。会不会在原图( eventMultiMap )中删除对应的值?
  • 好的,map的values上的迭代器可以移除values。谢谢。我现在不能接受你的回复,Iterator 没有编译,但编辑它,我会接受。
  • 对不起。工作中没有 google-collections,所以我无法在发布代码之前对其进行测试。如果它们的设计类似于 Java HashMap,则应该可以在 Entry 对象上使用 Iterator - 我已经编辑以显示这一点,如果您还没有机会尝试,我会在回家时再次检查它与此同时。
【解决方案2】:

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

【讨论】:

  • ...所以解决这个问题的方法是在您迭代它时检查值集合的大小,如果该值集合只剩下一个条目要从中删除,只需从键集的迭代器中删除即可。
【解决方案3】:

如果另一个线程可以在此逻辑运行时修改您的多图,您需要在 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) 逻辑。

【讨论】:

  • 我喜欢 removeAll(Collection.singleton(stuffToRemove))。谢谢。
【解决方案4】:

在 java8 中你也可以使用 lambda 方法:

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

【讨论】:

    【解决方案5】:

    如果您不关心密钥,我更喜欢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();
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-10-28
      • 1970-01-01
      • 2015-04-30
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 2013-09-29
      • 1970-01-01
      • 2019-01-02
      相关资源
      最近更新 更多