java并发修改异常

 

 迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。

一:问题代码:


import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Test {
    public static void main(String[] args) {
       Collection<String> c=new ArrayList<String>();
       c.add("a");
       c.add("b");
       c.add("c");
       
        Iterator<String> itr=c.iterator();
        
        while(itr.hasNext()){   //迭代器遍历,但是下面却用了集合的remove方法
            String str= (String)itr.next();
            if(str.equals("a"))
                c.remove("b");
        }
    }
}

 报错信息如下:

二:问题分析

问题出现场景:ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。

换句话说就是:迭代器遍历的时候只能用迭代器的remove()函数,如果用集合对象的remove函数就会爆出并发修改异常。

我们选择一步一步查看源代码:我们想着直接查 checkForComodification()函数,但是不行。所以我们先找到ArrayList,然而ArrayList类中根本没有迭代器的实现,所以我猜测Iterator大概是在他的父类或者接口中。

不出所料:在AbstractList类内果然发现了迭代器的实现函数,代码很简短,从这段代码可以看出返回的是一个指向Itr类型对象的引用,但是又出现了我们不认识的Itr(),所以接着去找

//下面是AbstractList的代码,只保留了内部类Itr和部分有用的代码。下面会拆开详细讲
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

        public Iterator<E> iterator() {
            return new Itr();
        }

        //可以看出Itr就是AbstractList的一个成员内部类
        private class Itr implements Iterator<E> {
            int expectedModCount = modCount;

            public boolean hasNext() {
                return cursor != size();
            }

            public E next() {
                checkForComodification();
                try {
                    int i = cursor;
                    E next = get(i);
                    lastRet = i;
                    cursor = i + 1;
                    return next;
                } catch (IndexOutOfBoundsException e) {
                    checkForComodification();
                    throw new NoSuchElementException(e);
                }
            }

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();

                try {
                    AbstractList.this.remove(lastRet);
                    if (lastRet < cursor)
                        cursor--;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException e) {
                    throw new ConcurrentModificationException();
                }
            }
//这就是最开始报错信息类的那个”罪魁祸首“函数,可以看出它的作用就是判断两个变量是否相等,那这个两个变量是什么呢?下面马上就讲。
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

 这里先讲两个变量:

expectedModCount:这个变量是AbstractList里面的内部类ltr中的变量。代表预期的修改次数,通过public Iterator<E> iterator() {  return new Itr(); }这个函数的时候会自动new一个Itr对象,同时会用modCount为其初始化。

      int expectedModCount = modCount;

modCount:这个是AbstractList类中的变量。表示实际的修改次数。

     protected transient int modCount = 0;

该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。

 好了,到这里我们再回到最初的程序:

  当调用c.iterator()返回一个Iterator之后,通过Iterator的hashNext()方法判断是否还有元素未被访问,我们看一下hasNext()方法,hashNext()方法的实现很简单:

public boolean hasNext() {
    return cursor != size();
}

  如果下一个访问的元素下标不等于ArrayList的大小,就表示有元素需要访问,这个很容易理解,如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了。

然后通过Iterator的next()方法获取到下标为0的元素,我们看一下next()方法的具体实现:

 

public E next() {
    checkForComodification();
 try {
    E next = get(cursor);
    lastRet = cursor++;
    return next;
 } catch (IndexOutOfBoundsException e) {
    checkForComodification();
    throw new NoSuchElementException();
 }
}

 

     这里是非常关键的地方:首先在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。

初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。

注意此时,modCount为0,expectedModCount也为0。

 接着往下看上面的出错程序,程序中判断当前元素的值是否为a,若为a,则调用对象中的remove()方法(而不是迭代器中的remove函数)来删除该元素。

    那我们先看一下在ArrayList中的remove()方法做了什么:

 

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
 
private void fastRemove(int index) {
    modCount++;   //注意这一步操作。其他的都可先不看
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    elementData[--size] = null; // Let gc do its work
}

通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

  那么注意此时各个变量的值:对于对象c的迭代器itr,其expectedModCount为0,cursor的值为1,lastRet的值为0。

  对于对象c本身,其modCount为1,size为0。

  接着看程序代码,执行完相应操作后,经过判断继续while循环,调用hasNext方法()判断,然后继续调用迭代器itr的next()方法:

  注意,此时要注意next()方法中的第一句:checkForComodification()。

  在checkForComodification方法中进行的操作是:

final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

 很显然,此时modCount为1,而expectedModCount为0,modCount不等于expectedModCount,因此程序就抛出了ConcurrentModificationException异常。

错误是在第一次while循环remove函数调用完埋下的,是第二次while循环next()函数时爆出来的。

  关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。也就是AbstractList的内部类中的Itr内部类只初始化一次,如果初始化之后再跳出内部类进行其他操作的话,modCount的值就可能改变,但是exceptedModCount的值却无法同步改变了。这就出错了,而且并发修改异常也就很容易理解为什么要这样叫了。

  注意,像使用for-each进行迭代实际上也会出现这种问题。

三:问题解决。

1:单线程下

上面以及说过了:迭代器遍历的时候用集合的函数修改就会抛出并发修改异常。  

 修改方法1.迭代器遍历,调用迭代器函数进行操作。

      Iterator<String> itr=c.iterator();
//1.迭代器遍历,调用迭代器的操作函数
       while(itr.hasNext()){
            String str= (String)itr.next();
            if(str.equals("a"))
                itr.remove();
        }

 修改方法2.传统的对象for循环遍历,调用对象函数进行操作。

     //2.选用普通的for循环遍历,调用对象的操作函数
       for(int i=0;i<c.size();i++){
           //这里必须进行一个强制的类型转换,取出来的是Object类型的对象,你得转换成String
              String str= ((ArrayList<String>) c).get(i);
              if(str.equals("a"))
                  c.remove("a");  
 //一个小细节,iterator里的remove函数不需要传参,对象的需要传参
       }

总之就是两条线路各走各的,不能交叉。

2:多线程下

多线程下的见下面的参考网址:https://www.cnblogs.com/bsjl/p/7676209.html

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ad_m1n

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值