先來看一段代碼,摘自阿里巴巴的java開發手冊
1 List<String> a = new ArrayList<String>(); 2 a.add("1"); 3 a.add("2"); 4 for (String temp : a) { 5 if("1".equals(temp)){ 6 a.remove(temp); 7 } 8 }
此時執行代碼,沒有問題,但是需要注意,循環此時只執行了一次。具體過程后面去分析。再來看一段會出問題的代碼:
List<String> a = new ArrayList<String>(); a.add("1"); a.add("2"); for (String temp : a) { if("2".equals(temp)){ a.remove(temp); } }
輸出為:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at luyudepackage.waitTest.main(waitTest.java:57)
是不是很奇怪?接下來將class文件,反編譯下,結果如下
1 List a = new ArrayList(); 2 a.add("1"); 3 a.add("2"); 4 Iterator i$ = a.iterator(); 5 do 6 { 7 if(!i$.hasNext()) 8 break; 9 String temp = (String)i$.next(); 10 if("1".equals(temp)) 11 a.remove(temp); 12 } while(true);
幾個需要注意的點:
1.foreach遍歷集合,實際上內部使用的是iterator。
2.代碼先判斷是否hasNext,然后再去調用next,這兩個函數是引起問題的關鍵。
3.這里的remove還是list的remove方法。
先去觀察下list.remove()方法中的核心方法fastRemove()方法。
1 private void fastRemove(int index) { 2 modCount++; 3 int numMoved = size - index - 1; 4 if (numMoved > 0) 5 System.arraycopy(elementData, index+1, elementData, index, 6 numMoved); 7 elementData[--size] = null; // clear to let GC do its work 8 }
注意第二行,modCount++,此處先不表,下文再說這個參數。
順路觀察下list.add()方法
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 }
注意第二行的注釋,說明這個方法也會使modCount++
再去觀察下,iterator()方法
1 public Iterator<E> iterator() { 2 return new Itr(); 3 }
1 private class Itr implements Iterator<E> { 2 int cursor; // index of next element to return 3 int lastRet = -1; // index of last element returned; -1 if no such 4 int expectedModCount = modCount; 5 6 public boolean hasNext() { 7 return cursor != size; 8 } 9 10 @SuppressWarnings("unchecked") 11 public E next() { 12 checkForComodification();//萬惡之源 13 int i = cursor; 14 if (i >= size) 15 throw new NoSuchElementException(); 16 Object[] elementData = ArrayList.this.elementData; 17 if (i >= elementData.length) 18 throw new ConcurrentModificationException(); 19 cursor = i + 1; 20 return (E) elementData[lastRet = i]; 21 } 22 23 public void remove() { 24 if (lastRet < 0) 25 throw new IllegalStateException(); 26 checkForComodification(); 27 28 try { 29 ArrayList.this.remove(lastRet); 30 cursor = lastRet; 31 lastRet = -1; 32 expectedModCount = modCount; 33 } catch (IndexOutOfBoundsException ex) { 34 throw new ConcurrentModificationException(); 35 } 36 } 37 38 final void checkForComodification() { 39 if (modCount != expectedModCount) 40 throw new ConcurrentModificationException(); 41 } 42 }
幾個需要注意的點:
1.在iterator初始化的時候(也就是for循環開始處),expectedModCount = modCount,猜測是和當時list內部的元素數量有關系(已證實)。
2.當cursor != size的時候,hasNext返回true
3.next()函數的第一行,checkForComodification()這個函數就是報錯的原因 這個函數就是萬惡之源
4.第39行,mod != expectedModCount 就會拋出ConcurrentModificationException()
接下來分析文章開頭的第一個例子,為啥不會報錯?
第一個例子執行完第一次循環后,mod = 3 expectedModCount =2 cursor = 1 size = 1 所以程序在執行hasNext()的時候會返回false,所以程序不會報錯。
第二個例子執行完第二次循環后,mod = 3 expectdModCount = 2 cursor = 2 size = 1 此時cursor != size 程序認定還有元素,繼續執行循環,調用next方法但是此時mod != expectedModCount 所以此時會報錯。
道理我們都懂了,再看一個例子
1 public static void main(String[] args) throws Exception { 2 List<String> a = new ArrayList<String>(); 3 a.add("1"); 4 a.add("2"); 5 for (String temp : a) { 6 System.out.println(temp); 7 if("2".equals(temp)){ 8 a.add("3"); 9 a.remove("2"); 10 } 11 } 12 }
此時輸出為:
1
2
顯然,程序並沒有執行第三次循環,第二次循環結束,cursor再一次等於size,程序退出循環。
與remove類似,將文章開頭的代碼中remove替換為add,我們會發現無論是第一個例子還是第二個例子,都會拋出ConcurrentModificationException錯誤。
原因同上,代碼略。
手冊上推薦的代碼如下
1 Iterator<String> it = a.iterator(); while(it.hasNext()){ 2 String temp = it.next(); if(刪除元素的條件){ 3 it.remove(); 4 } 5 }
此時remove是iterator的remove,我們看一下它的源碼:
1 public void remove() { 2 if (lastRet < 0) 3 throw new IllegalStateException(); 4 checkForComodification(); 5 6 try { 7 ArrayList.this.remove(lastRet); 8 cursor = lastRet; //index of last element returned;-1 if no such 9 lastRet = -1; 10 expectedModCount = modCount; 11 } catch (IndexOutOfBoundsException ex) { 12 throw new ConcurrentModificationException(); 13 } 14 }
注意第10行,第8行,所以此時程序不會有之前的問題。
但是手冊上推薦的方法,在多線程環境還是有可能出現問題,一個線程執行上面的代碼,一個線程遍歷迭代器中的元素,同樣會拋出CocurrentModificationException。
如果要並發操作,需要對iterator對象加鎖。
平時遍歷list,然后刪除某個元素的時候,如果僅僅刪除第一個且刪除之后調用break //代表着此時不會再去執行iterator.next方法 也就不會觸發萬惡之源
而如果要刪除所有的某元素,則會報錯,謹記!
Ps再來看一個佐證
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); for(int i : list){ System.out.println(i); if(i == 2){ list.remove((Object)2); } } }
此時只會輸出
1
2
當把remove對象改為3時候,再次報錯。