ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ArrayList并发修改问题-ConcurrentModificationException(详解)

2020-06-28 14:02:06  阅读:150  来源: 互联网

标签:var2 ConcurrentModificationException ArrayList arrayLists add 详解 new Integer


ArrayList并发修改问题-ConcurrentModificationException(详解)

有一次用到ArrayList的时候
需要遍历整个ArrayList找到指定元素再删除
于是便产生了下面的代码

public class ListError {
    public static void main(String[] args) {
        ArrayList<Integer> arrayLists = new ArrayList<>();
        arrayLists.add(new Integer(1));
        arrayLists.add(new Integer(2));
        arrayLists.add(new Integer(3));
        arrayLists.add(new Integer(4));
        arrayLists.add(new Integer(5));
        arrayLists.add(new Integer(6));
        arrayLists.add(new Integer(7));
        arrayLists.add(new Integer(8));

        for (Integer num:arrayLists) {
            if(num.equals(new Integer(3))){ 
                    arrayLists.remove(new Integer(3));            
                    }        
            }
    }
}

然后就产生了下面的错误:

Exception in thread "main" java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
 at java.util.ArrayList$Itr.next(ArrayList.java:851)
 at study.ListError.main(ListError.java:17)
程序抛出了ConcurrentModificationException并发修改异常 可以看到异常出现在checkForComodification方法中 查看ArraryList源码的checkForComodification方法
final void checkForComodification() {
        if (ArrayList.this.modCount != this.expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}

观察ArraryList源码中的checkForComodification方法
可以发现
当ArrayList.this.modCount != this.expectedModCount的时候会抛出异常

下面来看为什么会出现这两个值不相等的情况
在这之前,我们需要知道一个小知识
foreach底层也是使用Iterator迭代器进行迭代的

所以,最上面的源码的根本实现就是迭代器也就是这样

public class ListError {
    public static void main(String[] args) {
        ArrayList<Integer> arrayLists = new ArrayList<>();
        arrayLists.add(new Integer(1));
        arrayLists.add(new Integer(2));
        arrayLists.add(new Integer(3));
        arrayLists.add(new Integer(4));
        arrayLists.add(new Integer(5));
        arrayLists.add(new Integer(6));
        arrayLists.add(new Integer(7));
        arrayLists.add(new Integer(8));

        Iterator<Integer> iterator = arrayLists.iterator();
        while(iterator.hasNext()){
            Integer num = iterator.next();
            if(num.equals(new Integer(3))){
                arrayLists.remove();//importance
            }
        }

    }
    
}

从异常信息可以看到是在next()方法中调用了checkForComodification()
下面是ArraryList中next()方法的源码

public E next() {
    this.checkForComodification();
    int var1x = this.cursor;
    if (var1x >= SubList.this.size) {
        throw new NoSuchElementException();
    } else {
        Object[] var2x = ArrayList.this.elementData;
        if (var2 + var1x >= var2x.length) {
            throw new ConcurrentModificationException();
        } else {
            this.cursor = var1x + 1;
            return var2x[var2 + (this.lastRet = var1x)];
        }
    }
}

可见异常产生在 this.checkForComodification();这一句
下面我们依据自己的程序的源码来一步步看ArraryList源码的实现

debug程序在Iterator iterator = arrayLists.iterator();
进入到ArrayList

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

返回的是一个指向Itr的引用

下面是ArraryList的Itr类

private class Itr implements Iterator<E> {
    int cursor;
    int lastRet;
    int expectedModCount;

    private Itr() {
        this.lastRet = -1;
        this.expectedModCount = ArrayList.this.modCount;
    }

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

    public E next() {
        this.checkForComodification();
        int var1 = this.cursor;
        if (var1 >= ArrayList.this.size) {
            throw new NoSuchElementException();
        } else {
            Object[] var2 = ArrayList.this.elementData;
            if (var1 >= var2.length) {
                throw new ConcurrentModificationException();
            } else {
                this.cursor = var1 + 1;
                return var2[this.lastRet = var1];
            }
        }
    }

    public void remove() {
        if (this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            this.checkForComodification();

            try {
                ArrayList.this.remove(this.lastRet);
                this.cursor = this.lastRet;
                this.lastRet = -1;
                this.expectedModCount = ArrayList.this.modCount;
            } catch (IndexOutOfBoundsException var2) {
                throw new ConcurrentModificationException();
            }
        }
    }

    public void forEachRemaining(Consumer<? super E> var1) {
        Objects.requireNonNull(var1);
        int var2 = ArrayList.this.size;
        int var3 = this.cursor;
        if (var3 < var2) {
            Object[] var4 = ArrayList.this.elementData;
            if (var3 >= var4.length) {
                throw new ConcurrentModificationException();
            } else {
                while(var3 != var2 && ArrayList.this.modCount == this.expectedModCount) {
                    var1.accept(var4[var3++]);
                }

                this.cursor = var3;
                this.lastRet = var3 - 1;
                this.checkForComodification();
            }
        }
    }

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

首先我们来看三个变量
lastRet:上一个已经访问过的元素的索引
expectionModCount:修改次数的期望值
cursor:下一个要访问的元素的索引
此时lastRet=-1
cursor=0(初始值)
expectionModCount和modCount相等

可以发现,在调用add方法后modCount的值将会增加
在程序中调用了8次
即现在expectionModCount=modCount=8,
当lastRet=2, cursor=3时
也就是当下一个要被访问的元素是需要删除的元素,
调用next()方法获取这个元素
执行

this.cursor = var1 + 1;                   
return var2[this.lastRet = var1];

使得lastRet=3,cursor=4
然后程序判断该元素是否是需要被删除的
条件为真

调用arrayLists中的remove()方法
在源代码中观察arrayLists中的remove方法

public boolean remove(Object var1) {
    int var2;
    if (var1 == null) {
        for(var2 = 0; var2 < this.size; ++var2) {
            if (this.elementData[var2] == null) {
                this.fastRemove(var2);
                return true;
            }
        }
    } else {
        for(var2 = 0; var2 < this.size; ++var2) {
            if (var1.equals(this.elementData[var2])) {
                this.fastRemove(var2);
                return true;
            }
        }
    }

    return false;
}

this.fastRemove(var2);
发现最终调用的是fastRemove方法

private void fastRemove(int var1) {
    ++this.modCount;
    int var2 = this.size - var1 - 1;
    if (var2 > 0) {
        System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
    }

    this.elementData[--this.size] = null;
}

会发现modeCount的值发生了改变
现在modeCount=9而expectionModCount=8
虽然删除了元素
但是当再次调用hasNext()方法时需要调用 this.checkForComodification();
这时modeCount=9 != expectionModCount=8
抛出异常ConcurrentModificationException









下面问题来了
如何修改
细心地读者可能发现了
在Itr类中也有remove方法

public void remove() {
    if (this.lastRet < 0) {
        throw new IllegalStateException();
    } else {
        this.checkForComodification();

        try {
            ArrayList.this.remove(this.lastRet);
            this.cursor = this.lastRet;
            this.lastRet = -1;
            this.expectedModCount = ArrayList.this.modCount;
        } catch (IndexOutOfBoundsException var2) {
            throw new ConcurrentModificationException();
        }
    }
}

this.expectedModCount = ArrayList.this.modCount;

这里面关键的一句已经解决了这个问题

现在modeCount=9 = expectionModCount

修改过的代码如下

public class ListError {
    public static void main(String[] args) {
        ArrayList<Integer> arrayLists = new ArrayList<>();
        arrayLists.add(new Integer(1));
        arrayLists.add(new Integer(2));
        arrayLists.add(new Integer(3));
        arrayLists.add(new Integer(4));
        arrayLists.add(new Integer(5));
        arrayLists.add(new Integer(6));
        arrayLists.add(new Integer(7));
        arrayLists.add(new Integer(8));

        Iterator<Integer> iterator = arrayLists.iterator();
        while(iterator.hasNext()){
            Integer num = iterator.next();
            if(num.equals(new Integer(3))){
                iterator.remove();//importance
            }
        }
        System.out.println(arrayLists.toString());
    }
    /**
     * [1, 2, 4, 5, 6, 7, 8]
     */
}

到这里,问题就已经解决
如果对你有帮助
在这里插入图片描述也欢迎大家关注我的个人vx公众号:哪吒学编程

                                        等你来

标签:var2,ConcurrentModificationException,ArrayList,arrayLists,add,详解,new,Integer
来源: https://blog.csdn.net/ztlzlzl/article/details/106987229

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有