一、引言
我们先来看下面这个程序(修改集合有增添和删除两种,下面是以增添进行分析,删除同理)
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("world".equals(s)) {
list.add("javaee");
}
}
}
}
运行结果如下
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
at com.itheima_02.ListDemo.main(ListDemo.java:22)
当我们运行上面程序时,结果会报错
ConcurrentModificationException(并发修改异常)
ConcurrentModificationException:当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。
二、异常源码分析
1. 根据终端显示的异常信息,可以看到是第22行String s = it.next();
的next()方法报错,而这个next()方法是集合list通过调用iterator()方法产生对应迭代器的方法。我们先跟进一下Iterator<String> it = list.iterator();
的iterator()方法。
public Iterator<E> iterator() {
return new Itr();
}
2. 可以看到iterator()方法返回一个Iterator实现类对象Itr,再跟进一下Itr类
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
@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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
3. 从这个Itr内部类可以找到next()方法,执行next()方法的第一条语句是
checkForComodification();
我们再跟进一下这个方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
4. 可以看到这个方法做一个判断,判断modCount和expectedModCount是否相等如果不等就会抛出ConcurrentModificationException
异常。
modCount:实际集合修改次数
expectedModCount:预期修改次数
5. 我们看2中的Itr类的第一条语句int expectedModCount = modCount;
,这条语句保证了modCount与expectedModCount相等,但是当我们调用list的add()方法时就会修改modCount的值,使得二者不相等,再调用next()进行检查时就会抛出并发修改异常。下面看一下add()方法代码,可以看到在调用add()方法时,第一条语句会让modCount++操作,使得modCount与expectedModCount不相等。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
三、集合的三种遍历方式
并发修改异常通常发生在遍历集合时并对其进行修改,遍历集合有三种方式。
☀️ 第一种:普通for循环
【注释】普通for循环边遍历边修改集合不会出现并发修改异常
public class Test implements I {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("javaee");
list.add(1, "python");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
☀️ 第二种:增强for循环(推荐使用遍历集合的方式
)
【注释】增强for循环底层用的也是集合迭代器进行遍历,所以增强for循环和迭代器循环都会触发并发修改异常。
public class Test implements I {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("javaee");
list.add(1, "python");
for (String s : list) {
System.out.println(s);
}
}
}
☀️ 第三种:创建集合迭代器遍历
public class Test implements I {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("javaee");
list.add(1, "python");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}