前言
今天寫LeetCode遇到一道題,我想利用作爲方法參數的一個集合作爲返回的值,來達到節省空間的目的:
public List<Interval> merge(List<Interval> intervals) {}
意思就是,我想對集合intervals進行修改,然後返回值就傳修改後的intervals。
然後順勢就聯想到了併發修改異常,之前只是知道個概念,並沒有仔細思考過。今天就來分析一下ConcurrentModificationException。
ConcurrentModificationException
ConcurrentModificationException是開發中一個常見的異常,多發生於對一個Collection進行邊遍歷邊做影響size變化的操作時,比如說遍歷集合同時向集合中添加新的元素。
舉例
下面我們進行三種操作:
1.利用for循環遍歷集合的同時添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 2) {
list.add(10);
}
}
沒有問題。
2.利用迭代器遍歷集合的同時添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 2) {
list.add(10);
}
}
出現了問題,編譯器提示:Exception in thread “main” java.util.ConcurrentModificationException。
3.利用超級for循環遍歷集合的同時添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int num : list) {
if (num == 2) {
list.add(10);
}
}
同樣出現了問題,編譯器提示:Exception in thread “main” java.util.ConcurrentModificationException。
分析
以上使用的是ArrayList,那麼我們來看一下ArrayList中關於迭代器的源碼(Android SDK 23中的Open JDK源碼):
@Override public Iterator<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator<E> {
……
/** The expected modCount value */
private int expectedModCount = modCount;
@SuppressWarnings("unchecked") public E next() {
ArrayList<E> ourList = ArrayList.this;
int rem = remaining;
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (rem == 0) {
throw new NoSuchElementException();
}
remaining = rem - 1;
return (E) ourList.array[removalIndex = ourList.size - rem];
}
……
}
調用iterator()會返回一個ArrayListIterator對象,ArrayListIterator初始化的時候,會將外部類ArrayList(其實是ArrayList的父類AbstractList中的)的成員變量modCount的值賦給expectedModCount。
然而我們向集合中添加元素的時候,改變了modCount的值:
@Override public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
而expectedModCount的值沒有變,這時候再調用next(),會走進如下判斷分支:
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
拋出ConcurrentModificationException。
至於超級for循環遍歷呢?其實for-each是個語法糖,編譯器會把它轉化成迭代器遍歷,所以同樣會出錯。
iterator()不行,我們其實還可以使用listIterator(),它是ArrayList的父類AbstractList中的方法,它返回的是一個FullListIterator對象。我們增刪元素就利用FullListIterator的remove()和add(),如下:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ListIterator<Integer> listIterator = list.listIterator();
while (listIterator.hasNext()) {
if (listIterator.next().equals(2)) {
listIterator.add(10);
}
}
不會出現問題。
首先它的remove()和add()中調用的就是AbstractList的remove()和add()。
其次它會將expectedModCount的值與modCount的值進行同步,具體可以去查看源碼,這裏就不做分析了。