避免使用 Iterator.next() 的 ConcurrentModificationException



在我的Android应用程序中,我在地图上绘制一些航点时使用此代码

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

但是我收到此错误

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

我没有直接在我迭代的循环中修改列表。

但是我可能会在另一个线程中修改列表,因为用户可以移动一些航点。航点的绘制可以在用户使用触摸显示屏移动航点的同时进行。

我可以以某种方式避免这种异常吗?

如果要维护在多个线程中使用的List,最好使用并发列表,例如CopyOnWriteArrayList

在本地,您可以通过先创建航点列表的副本并迭代以下内容来避免异常:

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

数组列表提供的迭代器fail-fast迭代器 - 这意味着一旦修改底层列表,它就会失败。

避免异常的一种方法是将列表的快照拍摄到另一个列表中,然后循环访问它。

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

另一种方法是使用实现故障安全迭代器(如CopyOnWriteArrayList)的集合。

我看到一些选项:

a. 避免多线程。好吧,您不必完全避免多线程,只需访问数组即可。对数组的所有访问(甚至读取)都必须从同一线程进行。当然,繁重的计算可能会发生在其他一些线程上。当您可以快速迭代时,这可能是一种合理的方法。

b. 锁定数组列表,即使是为了阅读。这可能很棘手,因为过度锁定会导致死锁。

c. 使用数据副本。请记住,您只复制引用,但通常不必克隆所有对象。对于大型数据结构,可能值得考虑一些持久性数据结构,它不需要复制所有数据。

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

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

最新更新