Java ConcurrentModificationException异常原因和解决方法

时间:2022-06-01
本文章向大家介绍Java ConcurrentModificationException异常原因和解决方法,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Boy类:

public class Boy {

    //
    private String ID;

    private String name;

    private String age;

    public Boy() {
    }


    public Boy(String ID, String name, String age) {
        this.ID = ID;
        this.name = name;
        this.age = age;
    }

    public String getID() {
        return ID;
    }

    public void setID(String ID) {
        this.ID = ID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "ID='" + ID + ''' +
                ", name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

Girl :

import java.util.List;

public class Girl {


    private String ID;

    private String name;

    private String age;

    private List<Boy> boyList;


    public Girl() {
    }

    public Girl(String ID, String name, String age) {
        this.ID = ID;
        this.name = name;
        this.age = age;
    }

    public String getID() {
        return ID;
    }

    public void setID(String ID) {
        this.ID = ID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public List<Boy> getBoyList() {
        return boyList;
    }

    public void setBoyList(List<Boy> boyList) {
        this.boyList = boyList;
    }


    @Override
    public String toString() {
        return "Girl{" +
                "ID='" + ID + ''' +
                ", name='" + name + ''' +
                ", age='" + age + ''' +
                ", boyList=" + boyList +
                '}';
    }
}
import com.sun.deploy.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class TestList {


    public static void main(String[] args) {

        List<Girl> girls = new ArrayList<>();

        List<Boy> boys = new ArrayList<>();
        Boy boy = new Boy("21","xiaoming","3");
        Boy boy2 = new Boy("22","xiaohong","4");
        Boy boy3 = new Boy("23","xiaofang","5");
        Boy boy4 = new Boy("24","xiaomei","6");
        boys.add(boy);
        boys.add(boy2);
        boys.add(boy3);
        boys.add(boy4);

        Girl girl = new Girl("25","zhangfei","4");
        girl.setBoyList(boys);
        girls.add(girl);

        for(Girl girl1 : girls){
            List<Boy> boyList = girl.getBoyList();
            for(Boy boy1 :girl.getBoyList()){
                if("3".equals(boy1.getAge())){
                    boyList.remove(boy1);
                }
            }
        }

        for(Girl girl1 : girls){
            System.out.println("result :" + girl.toString());
        }

    }

}

有时候我们会在集合迭代循环的时候对集合机型add或者remove操作,通常我们的做法是用一个新的集合对象去存,然而这并不是一个高效的代码。最简单的做法是,在遍历集合的过程中对集合进行操作。蓝而,不幸的是,它抛出了以下异常:

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 TestList.main(TestList.java:30)

关于为什么会产生这个异常呢,异常类的注释里有描述:

/**
 * This exception may be thrown by methods that have detected concurrent
 * modification of an object when such modification is not permissible.
 * <p>
 * For example, it is not generally permissible for one thread to modify a Collection
 * while another thread is iterating over it.  In general, the results of the
 * iteration are undefined under these circumstances.  Some Iterator
 * implementations (including those of all the general purpose collection implementations
 * provided by the JRE) may choose to throw this exception if this behavior is
 * detected.  Iterators that do this are known as <i>fail-fast</i> iterators,
 * as they fail quickly and cleanly, rather that risking arbitrary,
 * non-deterministic behavior at an undetermined time in the future.
 * <p>
 * Note that this exception does not always indicate that an object has
 * been concurrently modified by a <i>different</i> thread.  If a single
 * thread issues a sequence of method invocations that violates the
 * contract of an object, the object may throw this exception.  For
 * example, if a thread modifies a collection directly while it is
 * iterating over the collection with a fail-fast iterator, the iterator
 * will throw this exception.
 *
 * <p>Note that fail-fast behavior cannot be guaranteed as it is, generally
 * speaking, impossible to make any hard guarantees in the presence of
 * unsynchronized concurrent modification.  Fail-fast operations
 * throw {@code ConcurrentModificationException} on a best-effort basis.
 * Therefore, it would be wrong to write a program that depended on this
 * exception for its correctness: <i>{@code ConcurrentModificationException}
 * should be used only to detect bugs.</i>

鉴于英文水平有限,我让有道爸爸给翻译了一下,大概是这样子的:

当检测到并发的方法修改不能修改的对象的时候有可能抛出这类异常。
例如,通常不允许一个线程修改集合,当另一个线程在上面迭代时。
一般来说,结果是在这种情况下迭代是没有定义的。一些迭代器实现(包括所有通用收集实现的实现)
由JRE提供的。
如果该行为是,可以选择抛出该异常检测。这样做的迭代器被称为< i>fail-fast迭代器,
当他们快速而干净地失败时,宁愿冒着任意的风险,
不确定的行为在未来不确定的时间。

请注意,这个异常并不总是表示对象有
*由< i >不同的< /i >线程同时进行修改。如果一个单一的线程发出了一系列的方法调用,
这些调用违背了对象的契约,对象可能抛出此异常。
例如,如果一个线程在集合中使用故障快速迭代器迭代器进行迭代的时候直接修改集合
*将抛出这个异常。
*
*
注意到,失败的快速行为通常不能得到保证
*说话时,在在场的情况下不可能做出任何强硬的保证
*不同步的并发修改。快速失败操作
*扔},{ @code ConcurrentModificationException力所能及。
*因此,编写一个依赖于此的程序是错误的
*例外的正确性:<我> { @code ConcurrentModificationException }
*应该只用于检测bug。< /i>

看完了这个解释,我觉得有道爸爸大体上是让我明白是怎么回事了。

接下来,我们看一下foreach的原理是什么。

在Java 1.5发行版本之前,对集合进行遍历的优先做法如下:

 //No longer the preferred idiom to iterate over a collection!
        for(Iterator i = c.iterator();i.hasNext();){
            doSomething((Element) i.next());//No generics before 1.5
        }

遍历数组的首选做法如下:

// No longer the preferred idiom to iterate over an array!
for (int i=0;i<a.length;i++){
    doSomething(a[i]);
}

看一下forEach的源码:

 default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

有道爸爸翻译一下注释:

为{@ code Iterable}的每个元素执行给定的操作
直到所有元素都被处理或操作抛出了一个
例外。除执行类另有规定外,
操作按照迭代的顺序执行(如果是迭代顺序的话)
指定)。由动作引发的异常被转发到
调用者。

迭代器模拟forEach:

 //No longer the preferred idiom to iterate over a collection!
        for(Iterator i = c.iterator();i.hasNext();){
            doSomething((Element) i.next());//No generics before 1.5
        }

首先是调用iterator()方法获得一个集合迭代器:

 /**
     * 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> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

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

        @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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

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

初始化时 expectedModCount记录修改后的个数,当迭代器能检测到expectedModCount是否有过修改

在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

解决这种异常的办法: