游戏开发中迭代List时增删实体报错的原因及解决方案验证
我正在开发一款游戏,用ArrayList管理所有实体的增删改操作。游戏每帧(60次/秒)会遍历列表里的所有实体执行更新和渲染。但当在实体的update方法里调用实体的添加或移除操作时,会在“Thread-2”中出现错误。
我初步知道迭代列表时修改列表是不对的,想请教下我遇到的是迭代时修改列表的问题,还是ArrayList非线程安全导致的问题?
我的渲染和更新方法代码如下:
渲染方法:
public void render(Graphics g){ for(Entity entity: list){ entity.render(g); } }
更新方法:
public void update(){ for(Entity entity: list){ entity.update(); } }
另外,如果问题根源是更新时修改列表,我想到了这个方案,请问是否可行?
public void update(){ for(Entity entity: list){ entity.update(); } for(Entity entity: removeList){ list.remove(entity); } removeList.clear(); }
嗨,我来帮你梳理下这个问题——刚好在游戏开发里经常碰到这类集合管理的坑!
先搞清楚问题根源:可能是两者的结合
迭代时修改列表的直接异常:你用的增强for循环(
for(Entity entity: list))底层是Java的Iterator实现的,而ArrayList的迭代器有**快速失败(fail-fast)**机制——一旦在迭代过程中列表的结构被修改(比如调用add()/remove()),就会立刻抛出ConcurrentModificationException。你的entity.update()里如果直接操作了原列表的增删,这就是触发错误的直接原因。线程安全的潜在风险:错误提示出现在“Thread-2”,说明你的游戏应该是多线程架构(比如主线程负责渲染,Thread-2跑更新逻辑?)。
ArrayList本身是非线程安全的,多个线程同时对它进行读写操作(比如一个线程在迭代渲染,另一个线程在更新时修改列表),哪怕没触发迭代器的快速失败,也可能出现数据错乱、索引越界甚至崩溃的情况——这是另一个需要注意的点。
所以你当前遇到的应该是迭代修改触发异常 + 多线程并发的潜在风险两者的叠加问题。
关于你提出的解决方案:完全可行,还可以优化
你这个延迟修改的思路,正是游戏开发里处理这类问题的标准操作之一,非常靠谱:
- 先完整遍历所有实体执行更新,把要移除的实体暂存到
removeList,而不是直接在迭代时碰原列表,完美避开了迭代器的快速失败机制。 - 等当前帧的更新逻辑全部跑完,再批量处理移除操作,这时候修改原列表就不会有任何问题了。
不过给你几个额外的优化建议:
- 如果有新增实体的需求,建议也搞一个
addList暂存,等更新迭代完成后再批量list.addAll(addList),避免新增实体干扰当前帧的逻辑。 - 如果是多线程场景,记得给
addList、removeList的读写操作加锁(比如用synchronized块),或者用线程安全的集合类(比如CopyOnWriteArrayList,不过它更适合读多写少的场景,批量增删还是暂存列表加锁更高效)。 - 注意
list.remove(entity)是依赖对象的equals()方法的,如果你的Entity类没正确重写equals()和hashCode(),可能会出现明明在removeList里却移除失败的情况——这时候可以考虑用实体的唯一ID或者索引来标记要删除的对象,更稳妥。
内容的提问来源于stack exchange,提问作者user9489163

