Java异常:Java.util.ConcurrentModificationException异常处理
·
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Java异常:Java.util.ConcurrentModificationException异常处理
前言
在开发中使用了迭代器对 List 进行遍历,遇到了java.util.ConcurrentModificationException报错,本文记录了这个错误出现的原因和如何解决
一、迭代器Iterator是什么?
迭代器是属于设计模式之一,迭代器模式提供了一种方法来顺序访问一个聚合对象中各个元素,而不保留该对象的内部表示。
1)Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
3)Iterator仅用于遍历集合,Iterator本身并不存放对象。
常见使用形式为:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TestDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
list.add(333);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
int value = iterator.next();
System.out.print(value + " ");
}
}
}
二、Java.util.ConcurrentModificationException原因
一个典型的代码实例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings({"all"})
public class TestDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
list.add(333);
System.out.println("删除前:" + list);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
if(value == 111) list.remove(Integer.valueOf(111));
}
System.out.println("删除后" + list);
}
}
问题就出现在:应该调用 Iterator.remove() 而不是 list.remove()!
运行结果中抛出java.util.ConcurrentModificationException异常信息。这是因为触发了集合中并发修改的异常 接下来我们通过源码对抛出异常的原因进行剖析。
public Iterator<E> iterator() {
return new Itr();
}
在ArrayList集合的Iterator方法中,是通过返回Itr对象来获得迭代器的。Itr是ArrayList的一个内部类,它实现了Iterator接口,代码如下:
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;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
Itr类属性
属性 | 含义 |
---|---|
cursor | 索引下标,表示下一个可以访问的元素的索引,默认值为 0 |
lastRet | 索引下标,表示上一个元素的索引,默认值为 -1 |
expectedModCount | 对集合修改的版本号,初始值为ModCount |
ModCount定义在AbstractList接口中,初始值为0,定义如下:
protected transient int modCount = 0;
ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。
结合上述代码进行抛出java.util.ConcurrentModificationException异常的解释。
①初始化ArrayList,添加三次元素,即三次调用add()方法,进行三次modCount++; 此时,m o d C o u n t = 3 , s i z e = 3 ; \color{red}{modCount = 3,size = 3;}modCount=3,size=3;
②初始化Iterator迭代器进行循环,此时,e x p e c t e d M o d C o u n t = m o d C o u n t = 3 , \color{red}{expectedModCount = modCount=3,}expectedModCount=modCount=3, c u r s o r = 0 , l a s t R e t = − 1 \color{red}{cursor=0,lastRet = -1}cursor=0,lastRet=−1
③进行hasNext判断,cursor != size;成立,进入循环
④调用next()方法,首先进行checkForComodification()校验,m o d C o u n t = = e x p e c t e d M o d C o u n t \color{red}{modCount == expectedModCount}modCount==expectedModCount,校验通过,返回值,此时l a s t R e t = 0 ; c u r s o r = 1 \color{red}{lastRet = 0;cursor = 1}lastRet=0;cursor=1
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
⑤调用集合remove()方法,modCount++;,此时}modCount=4;size=2
⑥再次调用hasNext()方法判断,cursor != size;成立,进入循环
⑦调用next()方法进行校验,modCount ! = expectedModCount,校验未通过,抛出java.util.ConcurrentModificationException异常
总结:
①在使用迭代器的remove()操作时,会将更新后的modCount给expectedModCount,两者会得到同步,但是在调用集合的remove()方法后,两个不会进行同步,进而导致在checkForComodification()校验时不通过,抛出java.util.ConcurrentModificationException异常。
②所以,在单线程下使用迭代器是没有问题的,但是在多线程下同时操作集合就不允许了,可以通过fail-fast快速失败机制,快速判断是否存在同时操作问题。因此,集合在多线程下使用是不安全的。
特别说明!
本文主要参考自: Java迭代器详解,看这一篇就够了
更多推荐
已为社区贡献4条内容
所有评论(0)