使用 Iterator.next() 避免 ConcurrentModificationException

Avoid ConcurrentModificationException using Iterator.next()

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

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

但是我收到这个错误

Fatal Exception: java.util.ConcurrentModificationException java.util.ArrayList$ArrayListIterator.next (ArrayList.java:573)

不是直接在我迭代的循环中修改列表。

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

我能以某种方式避免该异常吗?

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

在本地,您可以通过首先创建 waypoints 列表的副本并迭代它来避免异常:

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

array list 提供的迭代器是 fail-fast 迭代器 - 这意味着一旦底层列表被修改它就会失败。

避免异常的一种方法是将列表快照放入另一个列表,然后对其进行迭代。

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

另一种方法是使用实​​现 fail-safe 迭代器的集合,例如 CopyOnWriteArrayList.

我看到一些选项:

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

b。锁定 ArrayList,即使是为了阅读。这可能很棘手,因为过多的锁定会导致死锁。

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

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

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