java.util.ConcurrentModificationException异常详解

≡放荡痞女 提交于 2019-12-05 13:42:22

一、问题发现

在迭代集合元素时,如果对集合做add/remove操作,会抛出java.util.ConcurrentModificationException异常。

如下代码所示:

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		List<String> del = new ArrayList<String>();
		del.add("5");
		del.add("6");
		del.add("7");
		
		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
			String s = iter.next();
			if (del.contains(s)) {
				list.remove(s);
			}
		}
		
	}

}

执行上诉代码,会抛出异常:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
	at java.util.ArrayList$Itr.next(ArrayList.java:831)
	at com.wbf.list.ListDemo.main(ListDemo.java:23)

这是为什么呢?

二、问题分析

在集合中,如ArrayList,对它的做出修改操作(add/remove)时都会对modCount这个字段+1,modCount可以看作一个版本号,每次集合中的元素被修改后,都会+1(即使溢出)。接下来看看ArrayList从父类AbsrtactList中继承的iterator方法

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

这个方法返回内部类Itr的实例对象,类Iter实现了接口Iterator,代码如下

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

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

在内部类Itr中有一个属性expectedModCount,其默认的值等于modCount

int expectedModCount = modCount;

所以当我们调用集合类的iterator()方法时,返回迭代器对象时,expectedModCount被初始化为modCount

从内部类的代码中可以看出,它的remove()和next()方法中都调用了checkForComodification()方法,而这个方法做的事情就是判断modCount是否等于expectedModCount,如果不等于就抛出ConcurrentModificationException

在示例代码中,在进行集合迭代时,第一执行

String s = iter.next();

是不会有问题的,但是一旦满足条件,执行了

if (del.contains(s)) {
    list.remove(s);
}

如果集合还有元素未迭代,再次执行

String s = iter.next();

时,就会抛出:java.util.ConcurrentModificationException异常

这个异常是执行Itr类的next()方法时调用checkForComodification()抛出的。之所以会抛异常是因为初始化的时候modCount是等于expectedModCount的,但是在执行了一次

if (del.contains(s)) {
    list.remove(s);
}

后,modCount+1,在执行Itr类的next()方法时调用checkForComodification()发现modCount已经不等于expectedModCount了,所以抛出异常.

三、问题解决

1. 方法1

不要通过list.remove(s)来移除元素,而是iter.remove(),这样就没有了

为什么呢?看Itr类的remove()方法

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()方法的源码中,我们发现了十分重要的一行代码

expectedModCount = modCount;

一切都豁然开朗了吧。

2. 方法2

另外准备一个list用来保存需要移除的元素,在迭代完毕后一次性移除即可

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		List<String> del = new ArrayList<String>();
		del.add("5");
		del.add("6");
		del.add("7");
		
		List<String> needDel = new ArrayList<String>();
		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
			String s = iter.next();
			if (del.contains(s)) {
				//list.remove(s);
				//iter.remove();
				needDel.add(s);
			}
		}
		
		list.removeAll(needDel);
	}
}

这种方法就不用解释了吧。

四、问题补充

补充1:

在通过for(int i=0; i<list.size(); i++)方式遍历集合时,通过直接调用List类自身的remove()方法来移除元素,是完全没有问题的

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		for (int i=0; i<list.size(); i++) {
			list.remove(i);
		}
	}
}

理由很简单:虽然list.remove()会修改modCount的值,但是它没有调用checkForComodification()方法来校验modCount与expectedModCount是否相等,所以不会有java.util.ConcurrentModificationException异常的抛出

但是如果是通过foreach方式遍历集合的话,是会抛出java.util.ConcurrentModificationException异常的,代码如下:

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		for (String s : list) {
			list.remove(s);
		}
	}
}

因为foreach实现原理是转换为Iterator.next()来执行的。既然调用了Iterator的next()方法,必然需要通过checkForComodification()方法来校验modCount是否等于expectedModCount,此时由于list.remove(s)的原因,modCount与expectedModCount显然是不相等的,所以会抛出异常。

补充3:

其实从源码中可以看出,只有Itr类的remove()和next()方法在执行过程中调用了checkForComodification()来检查modCount是否等于expectedModCount,对于比如:get(),add()等方法都是没有做这项工作的,所以如果通过for(int i=0; i<list.size(); i++)遍历集合的时候,往集合中添加新的元素,是不会抛出ConcurrentModificationException异常的

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		int count = 0;
		for (int i=0; i<list.size(); i++) {
			if (count < 3) {
				list.add("count" + count++);
			}
			System.out.println(list.get(i));
		}
	}
}

执行完全没有问题。

但是这里必须将for(int i=0; i<list.size(); i++)与foreach区分开来

如果时foreach方式遍历集合时往集合中添加元素,执行时却会抛出java.util.ConcurrentModificationException异常,代码如下:

package com.wbf.list;

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

public class ListDemo {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");

		int count = 0;
		//for (int i=0; i<list.size(); i++) {
		for (String s : list) {
			if (count < 3) {
				list.add("count" + count++);
			}
		
			System.out.println(s);
		}
	}
}

这是为啥呢?因为foreach实现原理是转换为Iterator.next()来执行的。既然调用了Iterator的next()方法,必然需要通过checkForComodification()方法来校验modCount是否等于expectedModCount,此时由于list.add("count" + count++)的原因,modCount与expectedModCount显然是不相等的,所以会抛出异常。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!