ConcurrentModificationException甚至在LinkedHashMap上使用Collections.sychronizedMap

我在我的类中使用了一个Map对象,我已经与LinkedHashMap的Collections.synchronizedMap()同步,如下所示:

private GameObjectManager(){ gameObjects = Collections.synchronizedMap(new LinkedHashMap()); } 

我在这个函数的第三行得到一个并发修改exception:

 public static void frameElapsed(float msElapsed){ if(!INSTANCE.gameObjects.isEmpty()){ synchronized(INSTANCE.gameObjects){ for(GameObject object : INSTANCE.gameObjects.values()){...} } } } 

我正在迭代Map的所有其他位置,我按照文档在地图上进行同步。

我的类中还有其他函数使用这个Map(同步的!)和put()和remove()对象,但这应该不重要。 我究竟做错了什么? 请询问更多代码,不知道还有什么要放。

哦,和日志消息:

 08-20 15:55:30.109: E/AndroidRuntime(14482): FATAL EXCEPTION: GLThread 1748 08-20 15:55:30.109: E/AndroidRuntime(14482): java.util.ConcurrentModificationException 08-20 15:55:30.109: E/AndroidRuntime(14482): at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:350) 08-20 15:55:30.109: E/AndroidRuntime(14482): at java.util.LinkedHashMap$ValueIterator.next(LinkedHashMap.java:374) 08-20 15:55:30.109: E/AndroidRuntime(14482): at package.GameObjectManager.frameElapsed(GameObjectManager.java:247) 08-20 15:55:30.109: E/AndroidRuntime(14482): at package.GamekitInterface.render(Native Method) 08-20 15:55:30.109: E/AndroidRuntime(14482): at package.GamekitInterface.renderFrame(GamekitInterface.java:332) 08-20 15:55:30.109: E/AndroidRuntime(14482): at com.qualcomm.QCARSamples.ImageTargets.GameEngineInterface.onDrawFrame(GameEngineInterface.java:107) 08-20 15:55:30.109: E/AndroidRuntime(14482): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1516) 08-20 15:55:30.109: E/AndroidRuntime(14482): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240) 

尽管名称如此,但这与multithreading意义上的并发性无关。 除非在迭代器上调用remove() ,否则在迭代时不能修改此映射。 也就是说,你有……

 for(GameObject object : INSTANCE.gameObjects.values()){...} 

如果...修改INSTANCE.gameObjects.values() (例如,删除或添加元素),迭代器上的next()的下一次调用next() for循环是隐式的)将抛出该exception。

大多数集合和Map实现都是如此。 javadoc通常指定该行为,但并不总是显而易见。

修正:

  • 如果你要做的是删除元素,你需要显式获取Iterator并在其上调用remove()

     for (Iterator iter = INSTANCE.getObjects().values(); iter.hasNext(); ;) { GameObject object = iter.next(); if (someCondition(object)) { iter.remove(); } } 
  • 如果您正在尝试添加元素,则需要创建一个临时集合来保存要添加的元素,然后迭代器完成执行putAll(temporaryMapForAdding)

你正在使用for-each版本的for循环。 在Java中,禁止在此循环中添加或删除迭代集合中的元素。 要避免这种情况,请使用集合迭代器。 从迭代器中,您可以删除元素。

在迭代时, Collections.synchronizedMap()对您没有帮助。 这只会使你的地图以primefaces方式执行put / get / remove操作(这意味着你不会同时运行两个这样的操作)。

迭代时,您获取每个元素并使用它执行某些操作。 但是,如果您在迭代中作为当前元素执行的元素被其他某个线程删除了怎么办?

这是exception试图阻止的,因为您可能得到的结果与地图的任何实际快照都不对应:例如,如果您计算的是Integer数值的总和,那么您已经添加的元素可能是在您进行迭代时,可能会删除其他内容并添加其他内容,因此最终会得到与地图的任何“快照”都不匹配的总和。

对于您要做的事情,唯一的解决方案是在某个同步块中执行整个迭代,但必须在地图操作使用的同一监视器上进行同步。 Collections.syncrhonizedMap()提供了一个包装器,它在一些内部mutex同步, 而不是在this引用上同步。 因此,您尝试在迭代时阻止对地图进行任何修改将失败。