Java ConcurrentModificationException 解析

概述

ConcurrentModificationException 可以直接从字面理解:同时更改异常。也就是说,在对某一数据执行某一操作的时候,同时更改了数据,造成错误。

举个栗子

在遍历一个List同时remove其中的某个item

package com.example;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MyClass {
    static List<String> mList = new ArrayList<>();

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.initData();
        //forEach遍历同时remove某个item
        for (String s : mList) {
            if ("aaa".equals(s)) {
                mList.remove(s);
            }
        }
        myClass.iterator(mList);
        //模拟forEach遍历同时remove某个item
        Iterator<String> iterator = mList.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            if ("aaa".equals(s)) {
                mList.remove(s);
            }
        }
        myClass.iterator(mList);
        //iterator遍历同时remove某个item
        Iterator<String> iterator1 = mList.iterator();
        while (iterator1.hasNext()) {
            String s = iterator1.next();
            if ("aaa".equals(s)) {
                iterator1.remove();
            }
        }
        myClass.iterator(mList);
        //for遍历同时remove某个item
        for (int i = 0; i < mList.size(); i++) {
            String s = mList.get(i);
            if ("aaa".equals(s)) {
                mList.remove(s);
            }
        }
        myClass.iterator(mList);
    }

    public void initData() {
        mList.add("aaa");
        mList.add("bbb");
        mList.add("ccc");
        mList.add("ddd");
    }

    public void iterator(List<String> list) {
        for (String s : list) {
            System.out.println(s+"  ");
        }
    }
}

分别用这四种实现逻辑去遍历(注释其它的三种实现),打印结果依次是:

image.png
image.png
image.png
image.png

分析问题

从打印的结果我们可以看到前面两种实现会报ConcurrentModificationException ,其实这两种实现本质是一种,第一种forEach的方式其实是在第二种iterator上包了一层语法糖。第三种实现是iterator的正确实现方式,对比一下第二种,区别只是在mList.remove和iterator.remove,OK,这样就成功的缩小了问题范围。接下来,直接看源码:

/**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {    
        @SuppressWarnings("unchecked")
        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];
        }
        
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

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

重点看remove方法,ArrayList.this.remove(lastRet)之后,有一个expectedModCount = modCount。再看next()方法的checkForComodification():

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

问题就出在这里,iterator的remove方法有一个同步计数的逻辑。

总结

在遍历List的时候,如果对List数据有改动,不应用forEach的遍历方式,可以用for或者iterator来替代。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 传送门 解读阿里Java开发手册(v1.1.1) - 异常日志 前言 阿里Java开发手册谈不上圣经,但确实是大量...
    kelgon阅读 9,826评论 4 50
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 5,407评论 0 3
  • Java源码研究之容器(1) 如何看源码 很多时候我们看源码, 看完了以后经常也没啥收获, 有些地方看得懂, 有些...
    骆驼骑士阅读 4,571评论 0 22
  • 爱你是孤单的心事,是我想藏在心里不说又想说的争执,是我想说却又不敢说心思,是我想忘却又忘不掉的坚持,是我小心翼翼却...
    三之道阅读 4,662评论 0 1
  • 今天我从学校东门走到了北门,很奇怪吧!不就是从东门走到了北门吗?有什么了不起的。不,不是了不起。是我终于一个...
    久不烦阅读 3,707评论 0 0