《阿里巴巴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
好的,咱們看到已經定位到第一個斷點位置了,從idea提供的信息咱們也能夠看出list的大小爲2:
3d
接着往下走,又來到了第二個斷點的位置,在上面我已經說了forEach
的語法的原理了,因此這樣會走到haxNext()
函數這裏,這裏的cursor
是指當前迭代器的指針,而size
是當前集合的大小:
指針
繼續走,咱們會來到第三個斷點:
調試
注意我圈紅的第一處地方,咱們進入checkForComodification裏:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
複製代碼
能夠看到,這就是咱們報錯的關鍵點,這裏的modCount
變量是指集合被操做的次數,好比像add()
、remove()
這些方法都會讓modCount + 1
,而expectedModCount
是指集合的一個預期操做次數,在部分操做裏會被重置爲modCount
,好比add()
方法裏。
由於咱們上面添加了兩個元素,因此modCount
和modCount
都是2。
接着咱們看第二處圈紅的地方,咱們能夠發現,每一次next()
的時候指針都會移動,這很好理解。
斷點繼續,由於第一個元素就是1,因此這裏匹配上了:
咱們進入到remove()
方法裏面,由於咱們是按照對象刪除的,因此會進入第二個分支:
接着咱們再進入fastRemove()
方法,能夠看到modCount + 1
了:
繼續往下走,咱們又回到最開始的地方,但仔細點你會發現list的大小從2變成1了:
而後咱們又來到了hasNext()
這裏了,由於cursor
和size
都是1,因此循環就終止了:
這裏你是否是懵逼了,咦?說好的報錯呢?怎麼沒報錯了?
咳咳,實際上是由於有時候會出現像上面這種巧合的狀況,就是在hasNext()
方法校驗的時候,cursor
恰好不等於size
,而後就退出了,而恰好集合又遍歷完了,but,這個狀況是不多出現的,通常都會拋出ConcurrentModificationException
異常,因此你們不要有僥倖的心理。
下面咱們仍是以上面的例子,只是此次我把刪除的對象從1改成2:
運行調試後跟上面的P1和P2是同樣的,因此這裏我就不重複了,惟一不一樣的地方在P3。這裏咱們已經來到第二次循環,校驗元素後會刪除元素2:
在第三次循環,(這裏是指第三次進行hasNext()
)的時候,咱們能夠看到list的大小是1了:
ok,咱們繼續往下,這裏你們要特別注意,能夠看到cursor
此時是2,而size
倒是1,因此循環還能夠繼續:
前面咱們說過next()
方法裏的checkForComodification()
是檢查操做次數的,因此這裏就不復述了:
咱們進入到checkForComodification()
裏,能夠看到modCount
是3(由於remove()
操做**modCount**
自增了),而expectedModCount
是2,因此就報錯了: