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。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

解决这种异常的办法:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏wannshan(javaer,RPC)

dubbo通信消息解析过程分析(1)

由于rpc底层涉及网络编程接口,线程模型,网络数据结构,服务协议,细到字节的处理。牵涉内容较多,今天就先从一个点说起。 说说,dubbo通过netty框架做传...

3676
来自专栏Netkiller

Spring data mongodb @Document定义Enum/List/Map数据结构

本文节选自《Netkiller Java 手札》 7.2.2. @Document 复杂的 @Document 数据类型定义 package cn.netk...

3648
来自专栏于晓飞的专栏

读 Java Arrays 源码 笔记

Arrays.java是Java中用来操作数组的类。使用这个工具类可以减少平常很多的工作量。了解其实现,可以避免一些错误的用法。

632
来自专栏程序员互动联盟

读 Java Arrays 源码 笔记

Arrays.java是Java中用来操作数组的类。使用这个工具类可以减少平常很多的工作量。了解其实现,可以避免一些错误的用法。 它提供的操作包括: 排序 so...

35312
来自专栏Java开发

FastJson过滤字段

1、在对象对应字段前面加transient,表示该字段不用序列化,即在生成json的时候就不会包含该字段了。 比如

662
来自专栏后端之路

优先级队列之PriorityQueue

背景 前面几篇的队列基本都是和插入顺序相关的,一般来说可以先进先出,当然也可以通过双向队列实现进后出等等 那么在开发中还存在如下一些需求 不同的任务过来可以进入...

2887
来自专栏二进制文集

FastJSON 源码分析

Fastjson是一个Java语言编写的高性能功能完善的JSON库。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到极致,是目前Java...

1122
来自专栏Ryan Miao

如何从两个List中筛选出相同的值

问题 现有社保卡和身份证若干,想要匹配筛选出一一对应的社保卡和身份证。 转换为List<社保卡> socialList,和List idList,从二者中找出...

3039
来自专栏javathings

Spring 中,定时任务接口 SchedulingConfigurer

Spring 中,创建定时任务除了使用@Scheduled 注解外,还可以使用 SchedulingConfigurer。

513
来自专栏Ryan Miao

jackson简单使用,对象转json,json转对象,json转list

添加jackson依赖: // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/ja...

33211

扫码关注云+社区