所以这是我的问题,我有一个ArrayList,它包含应该呈现给屏幕的所有实体。
它使用foreach循环这样做。
for (Entity e : entities) {
g.fillRect(x, y, w, h);
}
当使用较少数量的值(例如50)填充时,这完全正常,没有错误。也就是说,列表的大小为50.但是当它大小为1,000时,会抛出ConcurrentModificationException并导致应用程序崩溃。
我知道异常意味着列表在迭代时已被修改,但在该循环中实际上从未对列表做任何事情。在其他地方访问该列表以更新内容,但是在修改列表的其他事情发生之前,foreach循环是否应该完成?
在更新实体的更新方法中修改了列表。
僵尸在某种意义上是敌人,幸存者是人工智能。当僵尸与AI发生碰撞时,它会移除幸存者并将其替换为幸存者。这是列表修改的唯一位置。
当它处理少量实体时,这一切都能很好地工作,但是数量较大的实体会崩溃。
public void update(double delta) {
for (Zombie z : zombies) {
z.update(delta);
}
for (Survivor s : survivors) {
s.update(delta);
}
List<Survivor> toRemove = new ArrayList<Survivor>();
List<Zombie> toAdd = new ArrayList<Zombie>();
for (Survivor s : survivors) {
for (Zombie z : zombies) {
if (z.collides(s)) {
toAdd.add(new Zombie(s.position, this, zms));
toRemove.add(s);
}
}
}
for (Survivor s : toRemove) {
survivors.remove(s);
}
for (Zombie z : toAdd) {
zombies.add(z);
}
}
答案 0 :(得分:2)
public void update(double delta)
这种方法听起来像是某种引擎调用的方法,几乎可以肯定是在另一个线程中。
for (Entity e : entities) {
g.fillRect(x, y, w, h);
}
这是在你的摇摆线程中运行的,它是独立的。
如果您拥有少量实体,则此操作可能会以原子方式完成。当你有更多的实体时,你有更高的机会交换到另一个线程,以便在绘制实体的过程中进行工作。
修复(我假设僵尸和幸存者是实体):
synchronized(entities)
{
for (Survivor s : toRemove) {
survivors.remove(s);
}
for (Zombie z : toAdd) {
zombies.add(z);
}
}
在你的油漆中:
synchronized(entities)
{
for (Entity e : entities) {
g.fillRect(x, y, w, h);
}
}
这将确保一次只有一个线程可以在其中一个同步块中,从而强制它们彼此分开发生
编辑:这有可能在发生碰撞后画一帧。如果您的帧速率足够高,这将是完全不明显的。如果你确实开始注意到它,那么你可能需要做更多的工作,这样一旦更新开始,涂料将不会开始直到完成。
答案 1 :(得分:2)
为避免同步,您应该考虑使用只读列表。
也就是说,在update()中,不要从列表中删除,而是将幸存者复制到新列表,然后将僵尸添加到该新列表中。最后,您可以将旧列表的引用替换为新列表中的一个。 这只需要在保存此引用的对象上进行同步。但是因为替换引用非常便宜,同步不会导致另一个trhread等待太久。
// pseudo code
newList = update(entities); // takes some time
synchronized (this) {
entities = newList; // is quasi-immediate
}
不要忘记让实体的getter同步,并且只能通过getter访问实体。