Java·ConcurrentModificationException的具體緣由

《阿里巴巴Java開發手冊》第一章裏的第五節的第七點是這麼說的:java

【強制】不要在 foreach 循環裏進行元素的 remove/add 操做。remove 元素請使用 Iterator 方式,若是併發操做,須要對 Iterator 對象加鎖。併發

裏面舉了這樣一個反例:ide

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");

for (String item : list) {
  if ("1".equals(item)) {
    list.remove(item);
  }
} 
複製代碼

其實Java的forEach寫法內部就是迭代器,你們能夠把上面的代碼理解爲如下代碼:函數

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
  String item = iterator.next();
  if ("1".equals(item)) {
    list.remove(item);
  }
}
複製代碼

有了這一層理解後,那咱們以ArrayList爲例,看看其內部的iterator方法:this

public Iterator<E> iterator() {
    return listIterator();
}

public ListIterator<E> listIterator(final int index) {
    checkForComodification();
    rangeCheckForAdd(index);
    final int offset = this.offset;

    return new ListIterator<E>() {
        hasNext()...
        next()...
        ...
    }
}             
複製代碼

因爲listIterator()方法內的內部類ListIterator的代碼太多,我就不一一貼出來了,由於咱們重點只看兩個方法:hasNext()next(),接下來我會經過斷點調試讓你們明白爲什ConcurrentModificationException是偶爾出現:idea

設置斷點

我在這三處地方都打了斷點,這樣咱們就能大概清楚整個流程:
spa

image.png

image.png

運行調試

P1

好的,咱們看到已經定位到第一個斷點位置了,從idea提供的信息咱們也能夠看出list的大小爲2:
3d

image.png

接着往下走,又來到了第二個斷點的位置,在上面我已經說了forEach的語法的原理了,因此這樣會走到haxNext()函數這裏,這裏的cursor是指當前迭代器的指針,而size是當前集合的大小:
指針

image.png

繼續走,咱們會來到第三個斷點:
調試

image.png

圈紅1

注意我圈紅的第一處地方,咱們進入checkForComodification裏:

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

能夠看到,這就是咱們報錯的關鍵點,這裏的modCount變量是指集合被操做的次數,好比像add()remove()這些方法都會讓modCount + 1,而expectedModCount是指集合的一個預期操做次數,在部分操做裏會被重置爲modCount,好比add()方法裏。

由於咱們上面添加了兩個元素,因此modCountmodCount都是2。

圈紅2

接着咱們看第二處圈紅的地方,咱們能夠發現,每一次next()的時候指針都會移動,這很好理解。

P2

斷點繼續,由於第一個元素就是1,因此這裏匹配上了:

image.png

咱們進入到remove()方法裏面,由於咱們是按照對象刪除的,因此會進入第二個分支:

image.png

接着咱們再進入fastRemove()方法,能夠看到modCount + 1了:

image.png

P3

繼續往下走,咱們又回到最開始的地方,但仔細點你會發現list的大小從2變成1了:

image.png

而後咱們又來到了hasNext()這裏了,由於cursorsize都是1,因此循環就終止了:

image.png

image.png

吃鯨

這裏你是否是懵逼了,咦?說好的報錯呢?怎麼沒報錯了?

咳咳,實際上是由於有時候會出現像上面這種巧合的狀況,就是在hasNext()方法校驗的時候,cursor恰好不等於size,而後就退出了,而恰好集合又遍歷完了,but,這個狀況是不多出現的,通常都會拋出ConcurrentModificationException異常,因此你們不要有僥倖的心理。

還原報錯

下面咱們仍是以上面的例子,只是此次我把刪除的對象從1改成2

image.png

運行調試後跟上面的P1和P2是同樣的,因此這裏我就不重複了,惟一不一樣的地方在P3。這裏咱們已經來到第二次循環,校驗元素後會刪除元素2:

image.png

在第三次循環,(這裏是指第三次進行hasNext())的時候,咱們能夠看到list的大小是1了:

image.png

ok,咱們繼續往下,這裏你們要特別注意,能夠看到cursor此時是2,而size倒是1,因此循環還能夠繼續

image.png

前面咱們說過next()方法裏的checkForComodification()是檢查操做次數的,因此這裏就不復述了:

image.png

咱們進入到checkForComodification()裏,能夠看到modCount是3(由於remove()操做**modCount**自增了),而expectedModCount是2,因此就報錯了

image.png
相關文章
相關標籤/搜索