[ConcurrentModificationException]關於對Map、List等集合操作拋出ConcurrentModificationException 異常問題

問題描述:

  • 先上代碼:

  • 會報一下錯:

分析原因:

  • 從報錯原因可以看到,首先是checkForComodification()方法異常,然後拋給next()方法。
  • 所以我們查看結合程序查看源碼:
ArrayList源碼:
 public Iterator<E> iterator() {
        return new Itr();
 }
 public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
  }
  public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;     
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

ArrayList$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();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
  • 從源碼中可以分析出,當我們 list.iterator()操作時,會new Itr()對象,而在Itr類中可以看到class Itr implements Iterator,實現了迭代器,在Itr類中首先要了解幾個成員變量:
    -cursor:表示下一個要訪問的元素的索引,從next()方法的具體實現就可看出
    -lastRet:表示上一個訪問的元素的索引
    -expectedModCount:表示對ArrayList修改次數的期望值,它的初始值爲modCount。
    -modCount是AbstractList類中的一個成員變量
protected transient int modCount = 0;
  • 該值表示對List的修改次數,查看ArrayList的add()和remove()方法就可以發現,每次調用add()方法或者remove()方法就會對modCount進行加1操作。

  • 當我們iterator.hasNext()方法調用時,會判斷cursor!=size(),也就是當下一個訪問元素不等於數組大小時,可以訪問,否則就到了數組的末尾。

  • 然後遊標下滑,取到元素:iterator.next(),這是首先會調用**checkForComodification()**這個方法進行驗證,將 int i = cursor; 根據i的值獲取下一個元素值,然後就是將i的值賦給lastRet,cursor進行+1操作。初始時,cursor爲0,lastRet爲-1,那麼調用一次之後,cursor的值爲1,lastRet的值爲0。注意此時,modCount爲0,expectedModCount也爲0。

  • 以此類推,當進行remove操作時,modCount++,這是expectedModCount依舊等於0,當進行下次while循環時,next()方法中第一行checkForComodification()檢查時就會拋出ConcurrentModificationException異常。

結論:
調用list.remove()方法導致modCount和expectedModCount的值不一致。

解決方案:
綜合上述分析原因,我們可以得到只要modCount = expectedModCount 就可以。
單線程情況:

  1. 使用Itr類中給出的remove()方法,相比ArrayList中的remove()多了一行代碼,即expectedModCount = modCount;
    所以我們可以將代碼改成:
  2. 下面的三種方式是大神總結,感覺很好,摘下來分享一下。親自測試過

**多線程情況:**這個暫時我還沒遇到過,摘自大神博客分享

  1. 原因結論:
    在多線程情況下,僅使用單線程遍歷中進行刪除的第1種解決方案使用it.remove(),但是測試得知4種的解決辦法中的1、3、4依然會出現問題。
    接着來再看一下JavaDoc對java.util.ConcurrentModificationException異常的描述:
    當方法檢測到對象的併發修改,但不允許這種修改時,拋出此異常。
    說明以上辦法在同一個線程執行的時候是沒問題的,但是在異步情況下依然可能出現異常。
  2. 嘗試方案
    (1) 在所有遍歷增刪地方都加上synchronized或者使用Collections.synchronizedList,雖然能解決問題但是並不推薦,因爲增刪造成的同步鎖可能會阻塞遍歷操作。
    (2) 推薦使用ConcurrentHashMap或者CopyOnWriteArrayList。
  3. CopyOnWriteArrayList注意事項
    (1) CopyOnWriteArrayList不能使用Iterator.remove()進行刪除。
    (2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);會出現如下異常:
    java.lang.UnsupportedOperationException: Unsupported operation remove
    at java.util.concurrent.CopyOnWriteArrayList$ListIteratorImpl.remove(CopyOnWriteArrayList.java:804)
  4. 解決方案
    單線程情況下列出4種解決方案,但是發現在多線程情況下僅有第2種方案才能在多線程情況下不出現問題。
final List<String> myList = new CopyOnWriteArrayList<String>();  
	 myList.add("1");  
	 myList.add("2");  
	 myList.add("3");  
	 myList.add("4");  
	 myList.add("5");  
	  
	new Thread(new Runnable() {  
	    
	     @Override  
	     public void run() {  
	          for (String string : myList) {  
	               System.out.println("遍歷集合 value = " + string);  
	              
	               try {  
	                    Thread.sleep(100);  
	               } catch (InterruptedException e) {  
	                    e.printStackTrace();  
	               }  
	          }  
	     }  
	}).start();  
	  
	new Thread(new Runnable() {  
	    
	     @Override  
	     public void run() {  
	         
	          for (int i = 0; i < myList.size(); i++) {  
	               String value = myList.get(i);  
	              
	               System.out.println("刪除元素 value = " + value);  
	          
	           if (value.equals( "3")) {  
	                myList.remove(value);  
	                i--; // 注意                             
	           }  
	          
	           try {  
	                    Thread.sleep(100);  
	               } catch (InterruptedException e) {  
	                    e.printStackTrace();  
	               }  
	          }  
	     }  
	}).start();  

對於Map,Set、List迭代操作都會出現這個問題,只用List來進行示例。

參考博客:
http://www.cnblogs.com/dolphin0520/p/3933551.html
https://blog.csdn.net/androiddevelop/article/details/21509345#insertcode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章