我正在尝试编写一个游戏,所以每个框架都调用doDraw()方法,我使用迭代器遍历所有GameObjects并将它们全部打印在屏幕上:
Iterator<GameObject> itr = mObjList.iterator();
while (itr.hasNext()) {
GameObject obj = itr.next(); // this line gives me the error
...
// print object
}
将项添加到列表的唯一方法是:
public void click(int x, int y) {
// adds new object to the list on a click event
mObjList.add(new GameObject(x, y));
}
大部分时间都有效。但有时候我会收到这个错误:
java.util.ConcurrentModificationException
从“itr.next()”行开始。从我用Google搜索,我认为这是因为click()事件有时会在draw()完成绘制每个对象之前发生,所以它在迭代器使用它时更改列表。我想这是错的?
但我对线程没有经验。我怎么可能解决这个问题?也许我做错了这一切我应该使用一种完全不同的方法在屏幕上打印所有对象?
答案 0 :(得分:4)
如果预期的读取和遍历次数大大超过列表的更新次数,请使用CopyOnWriteArrayList。
否则,在迭代和变异时列表(或专用互斥对象)上的synchronize
:
private List<GameObject> mObjList = /* whatever */;
private final Object mListMutex = new Object();
// snip...
synchronized (mListMutex) {
for (GameObject obj : mObjList) {
// do your thang
}
}
// snip...
public void click(int x, int y) {
GameObject obj = new GameObject(x, y);
synchronized (mListMutex) {
mObjList.add(obj);
}
}
答案 1 :(得分:2)
我想添加@aleph_null的答案。 @aleph_null是正确的,当您尝试在迭代时修改集合时会发生此异常 - 只允许迭代器上的remove()
方法。迭代器试图保护自己免受其下方集合发生的变化。
addAll()
。更多GC密集肯定但更清洁。
修改强>
抱歉,我错过了click()
是另一个线程传递的异步事件的事实。我假设在循环中调用了click()
。在click()
和addAll()
附近添加时,您必须在列表周围进行同步。您可以使用AtomicReference
来记录单击,然后在迭代器完成后对其执行操作,但前提是您只保证一次只能单击一个项目。
答案 2 :(得分:0)
解决方案是不使用迭代器,或以某种方式在列表之间进行乒乓,以便您正在修改的那个不是您正在迭代的那个。您也可以尝试使用同步,但这需要一些相当复杂的技能才能很好地完成而不会造成死锁。
答案 3 :(得分:0)
noooo,同步错误是令人讨厌的......特别是因为它们似乎是随机发生的。在使用迭代器遍历集合时,您无法修改集合(除非您使用迭代器的remove方法,但这是一个例外)。这样做会导致您正在查看的例外情况......但有时只会这样。只有在遍历迭代器时调用mObjList.add时才会发生错误。
答案 4 :(得分:0)
您对ConcurrentModificationException
获取原因的猜测是正确的。
要解决此问题,您可以在列表本身上进行同步:
synchronized(mObjList) {
Iterator itr = mObjList.iterator();
while (itr.hasNext()) {
// ...
}
}
这将允许您的线程获取列表本身的锁定。如果以类似的方式从另一个线程包装对列表的访问,它将阻止您的线程相互踩踏(参见Intrinsic Locks)。
我无法从上下文中判断这是否会对您的其他线程造成不必要的破坏,但如果列表很小,则不应该是问题。它肯定会比间歇性的例外引起更少的问题。