2

我搜索了 StackOverflow 并且有很多 ConcurrentModificationException 问题。读完之后,我还是一头雾水。我得到了很多这样的例外。我正在使用“注册表”设置来跟踪对象:

public class Registry {
    public static ArrayList<Messages> messages = new ArrayList<Messages>();
    public static ArrayList<Effect> effects = new ArrayList<Effect>();
    public static ArrayList<Projectile> proj = new ArrayList<Projectile>();

    /** Clears all arrays */
    public static void recycle(){
        messages.clear();
        effects.clear();
        proj.clear();
    }
}

我通过像这样访问 ArrayLists 向这些列表中添加和删除对象:Registry.effects.add(obj)Registry.effects.remove(obj)

我设法通过使用重试循环来解决一些错误:

//somewhere in my game..
boolean retry = true;
while (retry){
    try {
        removeEffectsWithSource("CHARGE");
        retry = false;
    }
catch (ConcurrentModificationException c){}
}

private void removeEffectsWithSource(String src) throws ConcurrentModificationException {
    ListIterator<Effect> it = Registry.effects.listIterator();
    while ( it.hasNext() ){
        Effect f = it.next();
        if ( f.Source.equals(src) ) {
            f.unapplyEffects();
            Registry.effects.remove(f);
        }
    }
}

但在其他情况下,这是不切实际的。我的drawProjectiles()方法中不断收到 ConcurrentModificationExceptions,即使它没有修改任何内容。我想罪魁祸首是如果我触摸了屏幕,它会创建一个新的 Projectile 对象并将其添加到 Registry.proj 而 draw 方法仍在迭代。

我不能很好地用draw方法做一个重试循环,否则它会重新绘制一些对象。所以现在我不得不找到一个新的解决方案。有没有更稳定的方法来完成我正在做的事情?

哦,我的问题的第 2 部分:很多人建议使用 ListIterators(正如我一直在使用的那样),但我不明白.. 如果我调用ListIterator.remove()它是否从它正在迭代的 ArrayList 中删除该对象,或者只是将其从迭代器本身?

4

2 回答 2

2

顶线,三个建议:

  • 不要做“在循环中包装异常”的事情。例外是针对例外情况,而不是控制流。(有效的 Java #57 或异常和控制流或“将异常用于控制流”的示例)
  • 如果您要使用 Registry 对象,请在该对象上公开线程安全的行为,而不是访问器方法,并在该单个类中包含并发推理。你的生活会变得更好。 禁止在公共领域暴露藏品。(ew,为什么是这些字段static?)
  • 要解决实际的并发问题,请执行以下操作之一:
    1. 使用同步集合(潜在的性能损失)
    2. 使用并发集合(有时逻辑复杂,但可能很有效)
    3. 使用快照(可能带有synchronizedReadWriteLock隐藏在封面下)

您问题的第 1 部分

对于多线程场景,您应该使用并发数据结构,或者使用同步器并制作防御性副本。可能直接将集合公开为public字段是错误的:您的注册表应该向这些集合公开线程安全的行为访问器。例如,也许你想要一个Registry.safeRemoveEffectBySource(String src)方法。将线程细节保留在注册表内部,这似乎是您设计中此聚合信息的“所有者”。

由于您可能并不真正需要List语义,因此我建议将这些替换为ConcurrentHashMapsWrapped into Setusing Collections.newSetFromMap()

您的draw()方法可以 a) 使用Registry.getEffectsSnapshot()返回集合快照的方法;或 b) 使用Iterable<Effect> Registry.getEffects()返回安全可迭代版本的方法(可能只是由 支持,在任何情况下ConcurrentHashMap都不会抛出)。CME我认为 (b) 在这里更可取,只要绘制循环不需要修改集合。这在 mutator 线程和draw()线程之间提供了一个非常弱的同步保证,但是假设draw()线程运行得足够频繁,错过更新或某些东西可能不是什么大问题。

您问题的第 2 部分

正如另一个答案所指出的,在单线程情况下,您应该确保使用Iterator.remove()删除项目,但同样,Registry如果可能的话,您应该将此逻辑包装在类中。在某些情况下,您需要锁定一个集合,对其进行迭代以收集一些聚合信息,并在迭代完成后进行结构修改。您询问该remove()方法是否只是将其从支持集合中Iterator或从支持集合中删除...请参阅API 合同,Iterator.remove()该合同告诉您它从基础集合中删除了对象。另请参阅此SO question

于 2010-11-05T02:07:31.283 回答
1

当您仍在对其进行迭代时,您不能直接从集合中删除一个项目,否则您将获得一个ConcurrentModificationException.

正如您所暗示的,解决方案是改为调用remove迭代器上的方法。这也会将它从底层集合中删除,但它会以 Iterator 知道发生了什么的方式执行此操作,因此当它发现集合已被修改时不会抛出异常。

于 2010-11-05T01:54:29.193 回答