天天看點

java.util.ConcurrentModificationException 出現的原因和解決辦法

用iterator周遊集合時碰到java.util.ConcurrentModificationException這個異常,

下面以List為例來解釋為什麼會報java.util.ConcurrentModificationException這個異常,代碼如下:

public static void main(String[] args) {
        List<String> saveList=new ArrayList<>();
        List<String> list=new ArrayList<>();
        for (int j=1; j < 100; j++) {
            list.add(String.valueOf(j));
        }
        Iterator<String> sss=list.iterator();
        while (sss.hasNext()) {
            if ("50".equals(sss.next())) {
                list.remove("50");
            }
        }
    } 

           

就是在周遊list的時候同時對list進行删除操作,就會報出java.util.ConcurrentModificationException,下面看一下 java.util.  AbstractList 的内部類 Itr 的源碼:

private class Itr implements Iterator<E> {  
    /** 
     * Index of element to be returned by subsequent call to next. 
     */  
    int cursor = 0;  
  
    /** 
     * Index of element returned by most recent call to next or 
     * previous.  Reset to -1 if this element is deleted by a call 
     * to remove. 
     */  
    int lastRet = -1;  
  
    /** 
     * The modCount value that the iterator believes that the backing 
     * List should have.  If this expectation is violated, the iterator 
     * has detected concurrent modification. 
     */  
    int expectedModCount = modCount;  
  
    public boolean hasNext() {  
            return cursor != size();  
    }  
  
    public E next() {  
            checkForComodification(); //檢測modCount和expectedModCount的值!!  
        try {  
        E next = get(cursor);  
        lastRet = cursor++;  
        return next;  
        } catch (IndexOutOfBoundsException e) {  
        checkForComodification();  
        throw new NoSuchElementException();  
        }  
    }  
  
    public void remove() {  
        if (lastRet == -1)  
        throw new IllegalStateException();  
            checkForComodification();  
  
        try {  
        AbstractList.this.remove(lastRet); //執行remove的操作  
        if (lastRet < cursor)  
            cursor--;  
        lastRet = -1;  
        expectedModCount = modCount; //保證了modCount和expectedModCount的值的一緻性,避免抛出ConcurrentModificationException異常  
        } catch (IndexOutOfBoundsException e) {  
        throw new ConcurrentModificationException();  
        }  
    }  
  
    final void checkForComodification() {  
        if (modCount != expectedModCount) //當modCount和expectedModCount值不相等時,則抛出ConcurrentModificationException異常  
        throw new ConcurrentModificationException();  
    }  
    }  
           

我們可以發現, ArrayList的remove方法隻是修改了modCount的值,并沒有修改expectedModCount,導緻modCount和expectedModCount的值的不一緻性,當next()時則抛出ConcurrentModificationException異常

是以使用Iterator周遊集合時,不要改動被疊代的對象,可以使用 Iterator 本身的方法 remove() 來删除對象,Iterator.remove() 方法會在删除目前疊代對象的同時維護modCount和expectedModCount值的一緻性。知道問題的原因就好辦了。

這裡總共提供3中方式處理這個問題:

1、建立一個saveList對象用于儲存要删除的值,等周遊完後調用list的removeAll方法删除:

for(String list1 : list){
            if("50".equals(list1)){
                saveList.add("50");
            }
        }
        list.removeAll(saveList);
           

2、使用Iterator替代增強型for循環 ,Iterator.remove()方法保證了modCount和expectedModCount的值的一緻性,避免抛出ConcurrentModificationException異常。

Iterator<String> sss=list.iterator();
        while (sss.hasNext()) {
            if ("50".equals(sss.next())) {
                sss.remove();
            }
        }
           

以上倆種方式在單線程環境下不會有問題,但是在多線程并發執行的情況下還是會出現問題,是以就引入第三種方式:使用CopyOnWriteArrayList替代ArrayList:

public static void main(String[] args) {
        List<String> list=new CopyOnWriteArrayList<>();
        for (int j=1; j < 100; j++) {
            list.add(String.valueOf(j));
        }
        Iterator<String> sss=list.iterator();
        while (sss.hasNext()) {
            if ("50".equals(sss.next())) {
                list.remove("50");
            }
        }
    } 
           

CopyOnWriteArrayList也是一個線程安全的ArrayList,其實作原理在于,每次add,remove等所有的操作都是重新建立一個新的數組,再把引用指向新的數組。

由于我用CopyOnWriteArrayList少,這裡就不多讨論了,想了解可以看: Java并發程式設計:并發容器之CopyOnWriteArrayList

對于hashMap也有類似的情況,看如下代碼:

public static void main(String[] args) {

        Map<String, String> map=new HashMap<>();
        for (int i=1; i < 100; i++) {
            map.put(String.valueOf(i), "testMap");
        }
        //将map中key的值都加上tf56字首
        for (String key : map.keySet()) {
            String value=map.get(key);
            map.remove(key);
            map.put("tf56" + key, value);
        }
    }
           

執行結果同樣會抛出:java.util.ConcurrentModificationException異常,解決的辦法也是非常的簡單:

用ConcurrentHashMap代替HashMap即可完美解決:

public static void main(String[] args) {

        Map<String, String> map=new ConcurrentHashMap<>();
        for (int i=1; i < 100; i++) {
            map.put(String.valueOf(i), "testMap");
        }
        //将map中key的值都加上tf56字首
        for (String key : map.keySet()) {
            String value=map.get(key);
            map.remove(key);
            map.put("tf56" + key, value);
        }
    }