避免使用Iterator.next()时出现ConcurrentModificationException异常

5
在我的Android应用中,当我在地图上绘制一些航路点时,我使用了这段代码。
Iterator<Waypoint> iterator = waypoints.iterator();
while (iterator.hasNext()) {
   Waypoint w = iterator.next();
}

但是我遇到了这个错误

致命异常:java.util.ConcurrentModificationException java.util.ArrayList$ArrayListIterator.next (ArrayList.java:573)

没有直接在循环中修改列表。

但是有可能我在另一个线程中修改了列表,因为用户可以移动一些路标。而当用户使用触摸显示器移动路标时,绘制路标也可能发生在同一时间。

我能否以某种方式避免此异常?


你不能创建一个对同一个列表的“另一个引用”吗?或者会“冲突”吗? - Santanu Sur
它不会起作用,因为底层列表将是相同的...只是引用会不同。 - Yogesh Badke
你的代码有多少次能够正确执行,又有多少次会出现ConcurrentModificationException异常?如果是例如每秒执行多次的绘制例程,我会捕获异常并重新开始迭代。因为用户通常反应很慢,不会一秒钟添加十几个点。 - Robert
3个回答

9
如果您想在多个线程中使用的List,最好使用并发列表,例如CopyOnWriteArrayList
在本地,您可以通过首先创建航点列表的副本并迭代该副本来避免异常:
Iterator<Waypoint> iterator = new ArrayList<>(waypoints).iterator();
while (iterator.hasNext()) {
    handle(iterator.next());
}

1

数组列表提供的迭代器是快速失败迭代器 - 这意味着只要基础列表被修改,迭代器就会失败。

避免异常的一种方法是将列表拍摄成另一个列表,然后对其进行迭代。

Iterator<Waypoint> iterator = new ArrayList<>(waypoints).iterator();
while (iterator.hasNext()) {
   Waypoint w = iterator.next();
}

另一种方法是使用实现了失败安全迭代器的集合,例如CopyOnWriteArrayList


在迭代之前每次复制数组列表是很昂贵的,特别是并发修改异常的概率非常低的情况下。 - Robert

-1

我看到了一些解决方案:

a. 避免多线程。好的,你不需要完全避免多线程,只要针对数组访问即可。所有对数组的访问(包括读取)都必须来自同一个线程。当然,可以在其他线程上进行繁重的计算。如果迭代速度很快,这可能是一个合理的方法。

b. 锁定ArrayList,即使是读取操作也要锁定。这可能有点棘手,因为过度锁定会引入死锁。

c. 使用数据副本。记住,你只复制引用,但通常不需要克隆所有对象。对于大型数据结构,考虑使用一些 persistent data structure 可能是值得的,这不需要复制所有数据。

d. 以某种方式处理ConcurrentModificationException。也许重新启动计算会有所帮助。在某些情况下,这可能很有用,但在复杂代码中可能会变得棘手。此外,在访问多个共享数据结构时,可能会出现活锁 - 两个(或更多)线程反复给彼此造成ConcurrentModificationException。

编辑:对于某些方法(至少在A方面),你可能会发现响应式编程很有用,因为这种编程风格减少了主线程中所花费的时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接