另一个ConcurrentModificationException问题

8 浏览
0 Comments

另一个ConcurrentModificationException问题

我在StackOverflow上搜索了很多关于ConcurrentModificationException的问题。阅读了这些问题后,我仍然感到困惑。我经常遇到这些异常。我使用一个\"Registry\"设置来跟踪对象:\n

public class Registry {
    public static ArrayList messages = new ArrayList();
    public static ArrayList effects = new ArrayList();
    public static ArrayList proj = new ArrayList();
    /** 清除所有数组 */
    public static void recycle(){
        messages.clear();
        effects.clear();
        proj.clear();
    }
}

\n我通过访问ArrayLists来添加和删除对象,例如:Registry.effects.add(obj)Registry.effects.remove(obj)\n我通过使用重试循环来解决一些错误:\n

//在我的游戏中的某个地方...
boolean retry = true;
while (retry){
    try {
        removeEffectsWithSource("CHARGE");
        retry = false;
    }
catch (ConcurrentModificationException c){}
}
private void removeEffectsWithSource(String src) throws ConcurrentModificationException {
    ListIterator it = Registry.effects.listIterator();
    while ( it.hasNext() ){
        Effect f = it.next();
        if ( f.Source.equals(src) ) {
            f.unapplyEffects();
            Registry.effects.remove(f);
        }
    }
}

\n但在其他情况下,这是不可行的。即使在drawProjectiles()方法中没有修改任何内容,我仍然会遇到ConcurrentModificationExceptions异常。我猜问题可能是当我触摸屏幕时,会在迭代绘制方法时创建一个新的Projectile对象并将其添加到Registry.proj中。\n我无法使用重试循环来处理绘制方法,否则它将重新绘制一些对象。所以现在我被迫寻找一个新的解决方案...有没有一种更稳定的方法来完成我正在做的事情?\n哦,还有问题的第二部分:很多人建议使用ListIterators(就像我一直在使用的那样),但我不明白...如果我调用ListIterator.remove(),它会从正在迭代的ArrayList中删除该对象,还是只从迭代器本身中删除它?

0
0 Comments

在遍历集合时,不能直接从集合中删除元素,否则会出现ConcurrentModificationException异常。

解决方法是,调用Iterator的remove方法。这样会从底层的集合中删除元素,并且Iterator知道集合已被修改,所以不会在发现集合已被修改时抛出异常。

0
0 Comments

另一个ConcurrentModificationException问题的原因是在多线程场景下没有使用并发数据结构或者使用同步器并进行了防御性拷贝。直接将集合作为public字段暴露出去是错误的,应该在Registry对象上提供线程安全的行为访问方法。例如,可以使用Registry.safeRemoveEffectBySource(String src)方法。将线程具体细节保持在Registry内部,因为Registry在设计中似乎是这个聚合信息的“拥有者”。

由于您可能并不真正需要List语义,建议将其替换为使用ConcurrentHashMap封装成Set,使用Collections.newSetFromMap()方法。

您的draw()方法可以使用Registry.getEffectsSnapshot()方法返回集合的快照,或者使用Iterable Registry.getEffects()方法返回一个安全的可迭代版本(可能只是由ConcurrentHashMap支持,不会在任何情况下抛出CME)。我认为后者更可取,只要draw循环不需要修改集合。这提供了一个非常弱的同步保证,确保mutator线程和draw()线程之间的同步,但是假设draw()线程运行得足够频繁,错过一个更新或其他操作可能并不重要。

对于单线程情况,可以使用Iterator.remove()方法来删除元素,但是如果可能的话,应该将此逻辑封装在Registry类中。在某些情况下,您需要锁定一个集合,在迭代完成后收集一些聚合信息,并进行结构性修改。您问remove()方法是从Iterator还是从支持集合中删除对象,可以查看Iterator.remove()的API约定,它告诉您它从底层集合中删除对象。还可以参考这个SO问题。

总之,最干净的方法是使用私有的非静态集合,并通过Registry类的方法进行访问。通过提供行为方法来访问私有集合,使Registry呈现出真实的数据模型,而不仅仅是一组无结构的集合。

0