Java - 并发修改异常

时间:2015-05-18 22:12:23

标签: java multithreading exception concurrency

我在以下代码中获得并发修改异常:

        for(Iterator<Tile> iter = spawner.activeTiles.iterator(); iter.hasNext();) {
            Tile tile = iter.next();
            canvas.drawRect(tile, tile.getColor());
        }

我理解并发修改是在迭代时更改(在迭代内部添加/删除)。我也明白,当我认为我的问题出现在多线程时,它们会发生。

在我的游戏中,我有几个在线程上运行的计时器。我有一个spawner,它在每个tick上为activeTiles添加值。然后我有一个2个计时器,一个用于淡入,一个用于淡出。在没有放弃我的游戏的情况下,当淡出完成时,或者当玩家点击牌时,牌基本上从列表中移除。因此,有一些情况会从切片列表中删除切片:

for(Iterator<Tile> iter = spawner.activeTiles.iterator(); iter.hasNext();) {
                Tile tile = iter.next();

                if(tile.contains(x, y) && tile.equals(spawner.activeTiles.get(0))) {
                    vibrator.vibrate(50);

                    tile.setBroken(true);
                    score ++;
                    spawner.setTileDelayInfo();
                    iter.remove();

在每个新的spawn之前,它会删除所有失败的tile:

private void removeFailedTiles() {
    for(Iterator<Tile> iter = activeTiles.iterator(); iter.hasNext();) {
        Tile tile = iter.next();

        if(tile.isFailed()) {
            iter.remove();
        }
    }
}

它几乎似乎是随机发生的。所以我认为它必须对时间做些什么,但我是这种例外的新手并且不知道该寻找什么或者为什么会发生这种情况。

4 个答案:

答案 0 :(得分:2)

好消息:你在问题中找到了问题的根本原因 - 你不能同时有多个线程访问列表,除非他们只是阅读。

您可以通过以下两种方式之一解决此问题,具体取决于代码的其余部分的运行方式。最正确的&#39;方法是steffen的回答:任何列表访问都应该使用synchronized块进行保护,并且包括在任何列表迭代的整个持续时间内保持锁定。请注意,如果你这样做,你希望在持有锁的同时做尽可能少的工作 - 特别是,在持有锁时做任何类型的监听器回调都是个坏主意。

您的第二个选择是使用CopyOnWriteArrayList,这是线程安全的,并且不需要任何外部同步 - 但对列表的任何修改(添加/删除/替换呼叫)都会变得更加昂贵

答案 1 :(得分:1)

多线程可以是ConcurrentModificationException的来源。当一个线程正在修改集合的结构而另一个线程有Iterator迭代它时,可能会发生这种情况。当一段代码需要一致的数据视图时,当集合的状态发生更改时,这可能会导致应用程序出现意外状态。当您在Tile s。

的集合上进行迭代时,需要这样做

您需要同步对activeTiles集合的访问权限。任何在结构上修改此集合(添加或删除)或迭代它的任何东西都必须在此集合上同步。

在迭代或结构修改synchronized (activeTiles)的所有代码周围添加activeTiles块。这包括您在此处提供的所有3个代码段。

或者,您可以使3个方法与您的代码段synchronized对应。

无论哪种方式,其他Thread都无法执行任何synchronized阻止,直到另一个Thread完成其syncrhonized部分,从而阻止了ConcurrentModificationException

答案 2 :(得分:1)

当您在另一个帖子中重复收集时,删除支持元素删除的Iterator元素是不安全的。

在迭代它们之前,在activeTiles的所有线程中获取Lock

答案 3 :(得分:1)

您可能希望使列表具有线程安全性。使用Collections.synchronizedList()

<script type="text/javascript" src="jquery-1.11.3.js"></script>
<script type="text/javascript">
  function countChars() {

      block1Chars++;
  }

  var block1Chars = 0;
</script>

请注意,在迭代它时必须在该列表上进行同步:

threadSafeActiveTiles = Collections.synchronizedList(activeTiles);

然后您可以安全地让多个线程修改列表,这似乎就是您的情况。

synchronized (threadSafeActiveTiles) { for (Iterator<Tile> it = threadSafeActiveTiles.iterator(); it.hasNext();) { Tile tile = it.next(); // ... } } 返回的列表使您不必在该列表的单个操作中使用synchronized块(上面),例如Collections.synchronizedList()add(e)size()和等......