1、問題描述java
話很少說,先上代碼:安全
public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>();
list.add("第零個"); list.add("第一個"); list.add("第二個"); list.add("第三個"); list.add("第四個"); for (String str : list) { if (str.equals("第三個")) { System.out.println("刪除:" + str); list.remove(str); } } System.out.println(list); }
知道快速失敗機制的可能都會說,不能在foreach循環裏用集合直接刪除,應該使用iterator的remove()方法,不然會報錯:java.util.ConcurrentModificationException函數
可是這個代碼的真實輸出結果倒是:this
並無報錯,這是爲何呢?spa
2、基礎知識線程
java的foreach 和 快速失敗機制仍是先簡單介紹一下:指針
foreach過程:code
Java在經過foreach遍歷集合列表時,會先爲列表建立對應的迭代器,並經過調用迭代器的hasNext()函數判斷是否含下一個元素,如有則調用iterator.next()獲取繼續遍歷,沒有則結束遍歷。blog
快速失敗機制:接口
由於非線程安全,迭代器的next()方法調用時會判斷modCount==expectedModCount,不然拋出ConcurrentModIficationException。modCount只要元素數量變化(add,remove)就+1,而集合和表的add和remove方法卻不會更新expectedModCount,只有迭代器remove會重置expectedModCount=modCount,並將cursor往前一位。因此在使用迭代器循環的時候,只能使用迭代器的修改。
3、分析
因此由上面的foreach介紹咱們能夠知道上面的foreach循環代碼能夠寫成以下形式:
for (Iterator iterator = list.iterator(); iterator.hasNext();) { String str = (String) iterator.next(); if (str.equals("第三個")) { System.out.println("刪除:" + str); list.remove(str); } }
重點就在於 iterator.next() 這裏,咱們看看next()的源碼:【此處的迭代器是ArrayList的私有類,實現了迭代器接口Iterator,重寫了各類方法】
1 public E next() { 2 checkForComodification(); 3 try { 4 int i = cursor; 5 E next = get(i); 6 lastRet = i; 7 cursor = i + 1; 8 return next; 9 } catch (IndexOutOfBoundsException e) { 10 checkForComodification(); 11 throw new NoSuchElementException(); 12 } 13 }
注意到第7行!,也就是說,cursor最開始是 i,調用next()後就將第 i 個元素返回,而後變成下一個元素的下標了,因此在遍歷到倒數第二個元素的時候cursor已經爲最後一個元素的下標(size-1)了,
注意了!在調用集合或者.remove(int)的方法時,並不會對cursor進行改變,【具體操做:將元素刪除後,調用System.arraycopy讓後面的的元素往前移動一位,並將最後一個元素位釋放】,而本程序中此時的size變成了原來的size-1=4,而cursor仍是原來的size-1=4,兩者相等!,再看看斷定hasNext():
public boolean hasNext() { return cursor != size(); }
此時cursor==size()==4,程序覺得此時已經遍歷完畢,因此根本不會進入循環中,也就是說根本不會進入到next()方法裏,也就不會有checkForComodification() 的判斷。
4、驗證
咱們在程序中foreach循環的第一句獲取str後面加入一個打印, System.out.println(str); ,
這樣每次進入foreach循環就會打印1,其餘不變,咱們再來運行一次,結果以下:
顯然,第四個元素沒有被遍歷到,分析正確,那假如使用iterator.remove()呢?
那咱們再來看看iterator.remove()的源碼,【此處的iterator是ArrayList的私有類,實現了迭代器接口Iterator,重寫了各類方法】
1 public void remove() { 2 if (lastRet < 0) 3 throw new IllegalStateException(); 4 checkForComodification(); 5 6 try { 7 AbstractList.this.remove(lastRet); 8 if (lastRet < cursor) 9 cursor--; 10 lastRet = -1; 11 expectedModCount = modCount; 12 } catch (IndexOutOfBoundsException e) { 13 throw new ConcurrentModificationException(); 14 } 15 }
看第7行,內部其實也是調用的所屬list的remove(int)方法,可是不一樣的地方要注意了:
第9行:將cursor--,也就是說在刪除當前元素後,他又把移動後的指針放回了當前元素下標,因此繼續循環不會跳過當前元素位的新值;
第11行:expectedModCount = modCount; 更新expectedModCount,使兩者相同,在繼續循環中不會被checkForComodification()檢測出報錯。
5、結論
1. 每次foreach循環開始時next()方法會使cursor變爲下一個元素下標;
2. ArrayList的remove()方法執行完後,下一個元素移動到被刪除元素位置上,由1可知,cursor此時指向原來被刪除元素的下一個的下一個元素所在位置,此時繼續foreach循環,被刪除元素的下一個元素不會被遍歷到;
3. checkForComodification()方法用來實現快速失敗機制的判斷,此方法在iterator.next()方法中,必須在進入foreach循環後纔會被調用;
4. 由2,當ArrayList的remove()方法在foreach刪除倒數第二個元素時,繼續foreach循環,倒數第一個元素會被跳過,從而退出循環,聯合3可知,在刪除倒數第二個元素後,並不會進入快速失敗機制的判斷。
5. iterator.remove()方法會在刪除和移動元素後將cursor放回正確的位置,不會致使元素跳過,而且更新expectedModCount,是一個安全的選擇。