1. 程式人生 > 其它 >Java併發修改異常的原始碼解析

Java併發修改異常的原始碼解析

技術標籤:Java基礎java

程式碼復現

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

for (String str : list) {
    if ("a".equals(str)) {
        list.remove("a");
    }
}

出現併發修改異常 ConcurrentModificationException


產生原因

at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
異常出現的位置出現在ArrayList類中內部類Itr中的checkForComodification方法

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

當一個名為modCount的變數值不等於expectedModCount的變數值時,異常物件被丟擲。

modCount:集合在結構上修改的次數

protected transient int modCount = 0;

expectedModCount:預測集合在結構上修改的次數

// 刪除指定索引位置上的物件
public E remove(int index) {
    rangeCheck(index);  // 檢查此索引是否存在

    modCount++; 		// modCount+1
    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); // 所呼叫方法內部modCount+1 return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); // 所呼叫方法內部modCount+1 return true; } } return false; } private void fastRemove(int index) { modCount++; // 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的原始碼中我們可以看到,當集合執行新增或者刪除操作時,modCount會發生改變(modCount++)。

當程式執行forEach遍歷時(如案例),實際是 呼叫Itertor的hasNext方法,然後再呼叫next方法。

Itertor 中維護了cursor, lastRet,expectedModCount 三個成員變數。

當在遍歷的時候執行remove 方法,modCount 會發生改變modCount++, 但是 expectedModCount並不會發生改變。因此在執行 next 方法的 checkForComodification 時,modCount++ 和 expectedModCount 並不相等,會丟擲ConcurrentModificationException。

next 方法:取出資料

public E next() {
    /**
    * 判定expectedModCount與modCount之間是否相等,如果不相等,則丟擲
    * concurrentModificationException
    **/
    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;    // 加一是為了使cursor變成下一次遍歷要取的值的索引
    return (E) elementData[lastRet = i]; // 給lastRet賦值,此時的i已經變成了上一次取出值的索引
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        // 當不相等時,跑出異常
        throw new ConcurrentModificationException();
}