You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java游戏中遍历LinkedList触发ConcurrentModificationException的解决方法

解决LinkedList遍历中移除元素导致的ConcurrentModificationException

嘿,这问题太常见了!你碰到的是Java集合迭代时的经典坑——增强for循环(也就是for-each)依赖的迭代器不允许在遍历过程中直接修改集合结构(比如remove/add操作),而你的代码刚好踩了这个雷。

问题根源拆解

你在InGame.tick()里用增强for遍历entities这个LinkedList,而遍历到Block对象时,它的tick()方法直接调用了inGame.entities.remove(this)——这会修改LinkedList的modCount(集合修改次数计数器),但当前迭代器维护的expectedModCount还是原来的值,两者不匹配,就触发了ConcurrentModificationException。

而且你的getSurrounding()方法还嵌套遍历了同一个集合,这只会让问题的触发概率更高,但核心原因还是遍历中直接修改集合。

三种可行解决方案

1. 标记待删除元素,遍历后批量移除(推荐,性能友好)

这种方式的思路是把移除操作和遍历操作分开,避免遍历过程中修改集合:

  • 给Entity类加一个删除标记字段和对应的方法:
public class Entity {
    private boolean markedForRemoval = false;

    public void markForRemoval() {
        markedForRemoval = true;
    }

    public boolean isMarkedForRemoval() {
        return markedForRemoval;
    }
}
  • 修改Block.tick(),把直接移除改成标记自己:
@Override 
public void tick(float delta) {
    // 保留你原来的周围方块判断逻辑...

    if (strength <= 0) {
        this.markForRemoval(); // 标记为待删除,不再直接调用集合remove
    }
}
  • 最后修改InGame.tick(),先遍历所有实体执行逻辑,再批量移除标记的元素:
@Override 
public void tick(float delta) {
    List<Entity> toRemove = new ArrayList<>();
    // 遍历执行tick,收集待删除元素
    for (Entity e : entities) {
        e.tick(delta);
        if (e.isMarkedForRemoval()) {
            toRemove.add(e);
        }
    }
    // 遍历完成后统一移除
    entities.removeAll(toRemove);
    player.tick(delta);
}

2. 使用显式迭代器的remove()方法(适合外部判断移除的场景)

如果是在遍历的外部逻辑中决定是否移除元素,而不是元素自己触发,可以用显式的Iterator遍历,调用迭代器的remove()方法——这个方法会同步更新modCount和expectedModCount,不会触发异常:

@Override 
public void tick(float delta) {
    Iterator<Entity> iterator = entities.iterator();
    while (iterator.hasNext()) {
        Entity e = iterator.next();
        e.tick(delta);
        // 如果是外部判断条件,比如这里判断Block是否要移除
        if (e instanceof Block && ((Block)e).getStrength() <= 0) {
            iterator.remove(); // 用迭代器的remove方法,而不是集合的remove
        }
    }
    player.tick(delta);
}

不过这种方式对你的场景不太适配,因为你的移除逻辑是在Block内部的tick()里,所以还是第一种标记法更合适。

3. 使用支持并发修改的集合(最省事,适合小体量游戏)

把LinkedList换成CopyOnWriteArrayList,这个集合的特点是遍历的时候会使用集合的快照,所有修改操作都会复制一份新的数组,不会影响正在进行的遍历,自然也就不会抛出异常:

// 把entities的声明改成CopyOnWriteArrayList
private List<Entity> entities = new CopyOnWriteArrayList<>();

这种方式不需要修改你原来的tick()和Block的移除逻辑,直接替换集合类型就行。缺点是每次修改都会复制数组,性能开销比LinkedList大,适合你的小游戏这种实体数量不多的场景。

最后再提一句

增强for循环看起来简洁,但本质是Iterator的语法糖,只要是遍历集合时修改集合结构(除了迭代器自己的remove()),都会触发这个异常——记住这个坑,以后就不会再踩啦!

内容的提问来源于stack exchange,提问作者Ruben de Groot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:15:59