ConcurrentModificationException
ConcurrentModificationException
ConcurrentModificationException 是 Java 中的一种运行时异常,通常发生在使用迭代器遍历集合(如 ArrayList, HashSet, HashMap 等)的同时,通过其他方式修改了集合的内容(添加、删除或更新元素)。这种情况下,Java 的集合框架为了保证数据的一致性和安全性,会抛出 ConcurrentModificationException 来阻止潜在的并发修改问题。
我相信很多初学Java的人一定会遇见这异常的。
public class Test30 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(0);
list.add(1);
for (Integer integer : list) {
if (integer == 1) {
list.add(2);
}
}
}
}
运行这段代码,会出现:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at com.example.mydemo.test.Test30.main(Test30.java:12)
探索ConcurrentModificationException
ConcurrentModificationException 主要由以下几种情况引起:
- 在迭代过程中直接修改集合:例如,在使用 Iterator 遍历一个集合时,直接调用集合的方法(如 add(), remove() 或 clear())来修改集合。
- 多个线程同时访问和修改同一个集合:如果一个线程正在遍历集合,而另一个线程试图修改它,可能会导致此异常。
- 使用增强的 for 循环(for-each loop)时修改集合:这是第一种情况的一个特例,因为增强的 for 循环内部实际上是使用了迭代器。
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
我们做foreach循环的时候,返回了Iterator对象,这个对象的modCount是新建立这个对象时候的modCount值。但是后面List.add的时候modCount+1了。导致后面Iterator判定预期修改值与实际修改值不相同。 下面是Itr的源码,我们明显看到expectedModCount是取得新建这个对象的时候modCount的值,导致后面集合修改了modCount值之后,就出现不一致的问题。
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
// 中间删除了一部分代码,有兴趣的同学 可以自己去源码里面去搜索
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
问题点就是checkForComodification的时候发现,预期修改次数与实际修改次数不相同,所以抛出了ConcurrentModificationException。这个解释有点像是反推,并不能让我们了解ConcurrentModificationException这个异常设计的根源问题。
实际上我们发现foreach循环最终转换成的是迭代器循环Iterator进行循环。而我们remove的时候调用的是Collection相关的对象。像上面那个代码。我们修改集合调用的是List对象。但是循环的时候又是采用的Iterator对象循环的。
看到问题点了吗
循环用的Iterator对象;修改集合用的是Collection对象。这就导致了一问题,如果要保证正确性,需要两个对象进行通信。通信就牵扯到了很多问题。最大的问题是什么? 是并发问题,所以为了保证正确性,引入了checkForComodification保证并发的正确性,这也导致了我们需要设计一个异常去处理这个问题。这就是为什么会有ConcurrentModificationException这个异常。
解决问题
上面我们已经清楚的知道了问题点,问题点在于循环用的Iterator对象;修改集合用的是Collection对象通信导致的。其实上面的那段代码,如果不采用集合去清理元素元素,就不会产生这样的错误。
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
但是Iterator对象实际上只有remove操作,没有新增元素的操作。 所以我们可以在删除元素的时候使用迭代器去删除,这样就更加安全。
总结
ConcurrentModificationException 是由于在遍历集合的同时对其进行修改所引起的常见问题。为了避免这种情况,开发者应根据具体的应用场景选择合适的解决策略,比如使用迭代器的安全方法、采用线程安全的集合类型、实施适当的同步措施或利用现代 Java 特性如流式 API。正确处理这个问题不仅可以提高程序的稳定性,还能改善其性能和可维护性。
** 现在你了解ConcurrentModificationException出现的原因了吗** 欢迎大家评论,留言,私信交流