Java併發修改異常的原始碼解析
阿新 • • 發佈:2021-01-29
程式碼復現
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();
}