并发修改异常原因探究,源码解析,解决方法

案例需求:

一个集合,里面有三个元素: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);
    }

 

posted @ 2020-04-07 22:25  硬盘红了  阅读(399)  评论(0编辑  收藏  举报