5

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

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(同步的!),它们 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)
4

3 回答 3

12

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

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

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

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

修复:

  • 如果您要删除元素,则需要显式获取Iterator<GameObject>并调用remove()它。

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

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

迭代时,您获取每个元素并对其进行处理。但是,如果您在迭代中作为当前元素所作用的元素被其他线程删除了怎么办?

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

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

于 2013-08-20T20:09:28.853 回答
2

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

于 2013-08-20T20:09:46.507 回答