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 条评论
登录 后参与评论

相关文章

来自专栏mathor

[kuangbin带你飞]专题一 简单搜索

 看到最短,最少之类的搜索题,基本都是用bfs,这道题大意是说,给一个三维的迷宫,要从S走到E,问最短走几步。普通的bfs是上下左右四个方向扩展,这个bfs...

1461
来自专栏java一日一条

Java 容器&泛型(1):认识容器

容器是Java语言学习中重要的一部分。泥瓦匠我的感觉是刚开始挺难学的,但等你熟悉它,接触多了,也就“顺理成章”地知道了。Java的容器类主要由两个接口派生而出:...

1212
来自专栏电光石火

HashSet/HashMap详解

HashMap和HashSet是Java Collection接口两个重要的成员,其中HashMap是Map接口常用的实现类,HashSet是Set接口常用...

23410
来自专栏用户3030674的专栏

Java 工具类—日期获得,随机数,系统命令,数据类型转换

1461
来自专栏数据结构与算法

2879 堆的判断

2879 堆的判断 时间限制: 1 s 空间限制: 32000 KB 题目等级 : 黄金 Gold 题目描述 Description 堆是一种常用...

3238
来自专栏Java爬坑系列

【Java入门提高篇】Day28 Java容器类详解(十)LinkedHashMap详解

  今天来介绍一下容器类中的另一个哈希表———》LinkedHashMap。这是HashMap的关门弟子,直接继承了HashMap的衣钵,所以拥有HashMap...

922
来自专栏用户画像

7.7.5 最佳归并树

文件经过置换-选择排序之后,得到的是长度不等的初始归并段。下面讨论如何组织初始归并段的归并顺序,使I/O访问次数最少。

791
来自专栏weixuqin 的专栏

数据结构学习笔记(线性表)

3085
来自专栏恰童鞋骚年

数据结构基础温故-4.树与二叉树(下)

上面两篇我们了解了树的基本概念以及二叉树的遍历算法,还对二叉查找树进行了模拟实现。数学表达式求值是程序设计语言编译中的一个基本问题,表达式求值是栈应用的一个典型...

1042
来自专栏腾讯IVWEB团队的专栏

ES6 中的 Set

ES6 新增了几种集合类型,本文主要介绍Set以及其使用。Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的...

8390

扫码关注云+社区

领取腾讯云代金券