我搜索过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对象,并在draw方法仍在迭代时将其添加到Registry.proj。
我不能很好地使用draw方法进行重试循环,否则它会重新绘制一些对象。所以现在我被迫找到一个新的解决方案..是否有一种更稳定的方式来完成我正在做的事情?
哦,我的问题的第2部分:许多人建议使用ListIterators(我一直在使用),但我不明白..如果我调用ListIterator.remove()
它是否从ArrayList中删除该对象它正在迭代通过,或者只是从迭代器本身中删除它?
答案 0 :(得分:2)
static
?)synchronized
或ReadWriteLock
)您应该为多线程方案使用并发数据结构,或者使用同步器并制作防御性副本。可能直接将集合公开为public
字段是错误的:您的注册表应该向这些集合公开线程安全的行为访问器。例如,您可能需要Registry.safeRemoveEffectBySource(String src)
方法。保持注册表内部的线程细节,这似乎是您设计中此聚合信息的“所有者”。
由于您可能并不真正需要List
语义,因此我建议使用ConcurrentHashMaps
将Collections.newSetFromMap()
替换为Set
替换它们。
您的draw()
方法可以a)使用返回集合快照的Registry.getEffectsSnapshot()
方法;或者b)使用返回安全可迭代版本的Iterable<Effect> Registry.getEffects()
方法(可能仅由ConcurrentHashMap
支持,在任何情况下都不会抛出CME
。我认为(b)在这里是优选的,只要绘制循环不需要修改集合。这在mutator线程和draw()
线程之间提供了非常弱的同步保证,但假设draw()
线程经常运行,缺少更新或某些事情可能不是什么大问题。< / p>
正如另一个答案所指出的,在单线程的情况下,你应该确保使用Iterator.remove()
来删除项目,但是再次,你应该将这个逻辑包装在Registry
类中。在所有可能的。在某些情况下,您需要锁定一个集合,迭代它以收集一些聚合信息,并在迭代完成后进行结构修改。您询问remove()
方法是否只是从Iterator
或后备集合中删除它...请参阅API contract for Iterator.remove()
,它告诉您它从基础集合中删除了对象。另请参阅此SO question。
答案 1 :(得分:1)
当您仍然在迭代时,您无法直接从集合中删除某个项目,否则您将获得ConcurrentModificationException
。
正如您所暗示的那样,解决方案是在Iterator上调用remove
方法。这也将从底层集合中删除它,但是它将以Iterator知道发生了什么的方式执行它,因此当它发现集合被修改时不会抛出异常。