案例需求:
一个集合,里面有三个元素:list.add("hello"); list.add("world"); list.add("java"); 遍历集合,如果有”world“这个元素,我们就添加一个”javaee“元素进去。
代码如下:
public class list_03 { public static void main(String[] args) { //创建集合对象 List<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("world"); list.add("java"); //遍历集合 Iterator<String> it = list.iterator(); while (it.hasNext()){ String s = it.next(); if (s.equals("world")){ list.add("javaee"); } } //输出集合对象 System.out.println(list); //java.util.ConcurrentModificationException } }
此时程序会发生运行异常:java.util.ConcurrentModificationException
通过具体的异常提示,我们来看看是哪里发生的问题:
由提示可知,问题出现在第20行(由于删除了包名,和导包,所以导致上图中行号错误,在上图中是第14行),也就是it.next()方法上,而通过源码分析可知,next是由Itr内部类通过实现Iterator接口并重写的方法,而被重写的next方法,直接调用了checkForComodification();方法;在checkForComodification();方法中,由于两个成员变量不相等,从而抛出ConcurrentModificationException
public class ArrayList<E> extends AbstractList<E> implements List<E>{ public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { int expectedModCount = modCount; 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(); } } }
进而,问题的探究也就变成了,是什么原因造成二者不相等,继续分析源码可知,在list.add方法中,修改了modCount++的值,从而导致两者不相等。
public boolean add(E e) { modCount++; ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
modCount :实际修改集合次数
expectedModCount : 预期修改集合次数
至此,是什么造成“并发修改异常”的原因(原因不唯一,因为前面checkForComodification()中需要比较二者,也可以使用其他方法不必比较二者的值来完成遍历)已经找到,那么可以通过什么方法解决呢? 使用for循环遍历集合
for (int i = 0;i<list.size();i++){ String s = list.get(i); if (s.equals("world")){ list.add("javaee"); } }
//[hello, world, java, javaee]
需要注意的是,我仍然使用了list.add方法,但是在获取集合时,使用了get方法,而在gei方法源码中,并不需要比较两个成员变量的数值,所以即使修改了modCount的值,也不会抛出异常。
List.get方法源码如下:
public E get(int index) { rangeCheck(index); return elementData(index); }
原文:https://www.cnblogs.com/pxy-1999/p/12656637.html