ConcurrentModificationException甚至在LinkedHashMap上使用Collections.sychronizedMap

时间:2013-08-20 19:59:53

标签: java multithreading synchronized linkedhashmap concurrentmodification

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

private GameObjectManager(){
        gameObjects = Collections.synchronizedMap(new LinkedHashMap<String, GameObject>());
}

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

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

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

我的类中还有其他函数使用这个Map(同步的一个!),并且它们放置()和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)

3 个答案:

答案 0 :(得分:12)

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

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

如果...修改INSTANCE.gameObjects.values()(例如,删除或添加元素),则迭代器上的next()的下一次调用(隐含于for ())会抛出异常。

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

修正:

  • 如果您要删除该元素,则需要明确获取Iterator<GameObject>并在其上调用remove()

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

答案 1 :(得分:2)

迭代时,

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

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

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

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

答案 2 :(得分:2)

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