美文网首页程序员
ConcurrentModificationException-

ConcurrentModificationException-

作者: alonwang | 来源:发表于2018-11-12 20:26 被阅读26次

    HashMap中关于这点的说明

    <p>The iterators returned by all of this class's "collection view methods"
    are <i>fail-fast</i>: if the map is structurally modified at any time after
    the iterator is created, in any way except through the iterator's own
    <tt>remove</tt> method, the iterator will throw a
    {@link ConcurrentModificationException}. Thus, in the face of concurrent
    modification, the iterator fails quickly and cleanly, rather than risking
    arbitrary, non-deterministic behavior at an undetermined time in the
    future.
    <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
    as it is, generally speaking, impossible to make any hard guarantees in the
    presence of unsynchronized concurrent modification. Fail-fast iterators
    throw <tt>ConcurrentModificationException</tt> on a best-effort basis.
    Therefore, it would be wrong to write a program that depended on this
    exception for its correctness: <i>the fail-fast behavior of iterators
    should be used only to detect bugs.</i>

    如果在使用iterator(或者foreach,它实质上就是iterator)遍历元素时通过源列表直接删除元素会导致ConcurrentModificationException,jdk的设计者本意是fast-fail
    1.用来对出现并发问题时提前报错,防止问题扩散难以查找原因
    2.禁止在遍历时在外部修改源集合导致数据不一致问题,这个是错误使用
    下面这个就是2的具体表现

    public class ConcurrentModifyExcetionTest {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            Iterator<Integer> itr = list.iterator();
            while (itr.hasNext()) {
                Integer element = itr.next();
                list.remove(element);
            }
        }
    }
    
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
        at java.util.ArrayList$Itr.next(ArrayList.java:859)
        at com.github.alonwang.other.ConcurrentModifyExcetionTest.main(ConcurrentModifyExcetionTest.java:18)
    

    下面通过源码分析
    list.iterator(),可以看到返回了AbstractList的一个内部类Itr(PS.这里有个面向对象的常识,如果本类中找不到对应的方法,就去父类/接口中找)

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

    Itr,它是迭代器的一个内部实现,它最重要的字段就是expectedModCount

    • expectedModCount 预期被修改的次数,属于Itr私有,初始时和modCount相等
    • modCount 集合被结构性修改(新增或删除)的次数,它是属于集合的

    使用itr进行遍历/删除时都会进行checkForComodification()检查,只有在使用itr的remove()来移除元素,expectedModCount才会被更新,如果通过源集合直接删除,modCount会更新expectedModCount不会发生变化,也就导致modCount!=expectedModCount进而报错

    final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    
    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();
                }
            }
    

    对于单线程程序,正确的用法是这样的

    public class ConcurrentModifyExcetionTest {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            Iterator<Integer> itr = list.iterator();
            while (itr.hasNext()) {
                Integer element = itr.next();
                //使用itr的remove方法来移除
                itr.remove();
            }
        }
    }
    

    这有引出另外一个问题,多线程下使用itr.remove()还会出现ConcurrentModificationException吗?
    答案是 会的.问题的核心在于itr是线程私有的,这隐含着expectedModCount也是线程私有的.而modCount是线程共享的.
    如果有一个线程对集合进行了结构性修改,那么modCount和此线程的expectedModCount会更新,其他线程的expectedModCount都不会更新,也就势必导致其他线程的expectedModCount!=modCount,最终导致ConcurrentModificationException

    总结

    ConcurrentModificationException是集合对并发的一种自我防御机制,它通过预先检查提前报错来防止更严重的问题,如果遇到这个问题要思考一下原因

    1. 自己使用错误
    2. 需要换用线程安全的集合如CopyOnWriteArrayList

    https://my.oschina.net/hosee/blog/612718

    相关文章

      网友评论

        本文标题:ConcurrentModificationException-

        本文链接:https://www.haomeiwen.com/subject/mfthfqtx.html