同时迭代Java ArrayList时会发生NoSuchElementException

Kev*_*vin 11 java arraylist

我有一个类似于下面的方法:

public void addSubjectsToCategory() {
    final List<Subject> subjectsList = new ArrayList<>(getSubjectList());
    for (final Iterator<Subject> subjectIterator =
            subjectsList.iterator(); subjectIterator.hasNext();) {
         addToCategory(subjectIterator.next().getId());
    } 
}
Run Code Online (Sandbox Code Playgroud)

当它同时为同一个用户(另一个实例)运行时,有时它会抛出NoSuchElementException.根据我的理解,有时subjectIterator.next()在列表中没有元素时执行.仅在访问时会发生这种情况.方法同步会解决这个问题吗?

堆栈跟踪是:

java.util.NoSuchElementException: null
at java.util.ArrayList$Itr.next(Unknown Source)
at org.cmos.student.subject.category.CategoryManager.addSubjectsToCategory(CategoryManager.java:221)
Run Code Online (Sandbox Code Playgroud)

该堆栈跟踪失败addToCategory(subjectIterator.next().getId());.

jur*_*rez 9

迭代器的基本规则是在使用迭代器时不得修改底层集合.

如果你有一个单独的线程,这个代码似乎没有任何问题,只要getSubjectsList()不返回null addToCategory()或者getId()有一些奇怪的副作用会修改subjectsList.但请注意,您可以将for循环重写得更好(for(Subject subject: subjectsList) ...).

从您的代码判断,我最好的猜测是你有另一个线程正在修改subjectsList其他地方.如果是这种情况,使用SynchronizedList可能无法解决您的问题.据我所知,同步仅适用于列出的方法,如add(),remove()等,迭代过程中不锁定的集合.

在这种情况下,添加synchronized到该方法也无济于事,因为另一个线程在其他地方做了令人讨厌的事情.如果这些假设是正确的,那么最简单和最安全的方法是创建一个单独的同步对象(即Object lock = new Object())然后放置synchronized (lock) { ... }此for循环以及程序中修改集合的任何其他位置.这将阻止其他线程在此线程迭代时进行任何修改,反之亦然.

  • 但是,有一种不同的可能性 - 我看过`ArrayList.Itr.next()`并且它能够产生`NoSuchElementException`的唯一方法就是当它认为它的大小实际上与底层数组不同时.如果在复制原始列表时修改原始列表,这可能意味着在复制期间出现问题(`new ArrayList <>(getSubjectList())`). (2认同)