java.util.ConcurrentModificationException異常詳解

1、問題發現java

在迭代集合元素時,若是對集合作add/remove操做,會拋出java.util.ConcurrentModificationException異常。this

以下代碼所示:code

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

}

執行上訴代碼,會拋出異常:xml

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)

這是爲何呢?對象

2、問題分析繼承

在集合中,如ArrayList,對它的作出修改操做(add/remove)時都會對modCount這個字段+1,modCount能夠看做一個版本號,每次集合中的元素被修改後,都會+1(即便溢出)。接下來看看ArrayList從父類AbsrtactList中繼承的iterator方法接口

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

這個方法返回內部類Itr的實例對象,類Iter實現了接口Iterator,代碼以下element

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,其默認的值等於modCountrem

int expectedModCount = modCount;

因此當咱們調用集合類的iterator()方法時,返回迭代器對象時,expectedModCount被初始化爲modCountget

從內部類的代碼中能夠看出,它的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了,因此拋出異常.

3、問題解決

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

這種方法就不用解釋了吧。

4、問題補充

補充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顯然是不相等的,因此會拋出異常。

相關文章
相關標籤/搜索