1. 程式人生 > >java.util.ConcurrentModificationException異常 分析

java.util.ConcurrentModificationException異常 分析

ArrayList是java開發時非常常用的類,常碰到需要對ArrayList迴圈刪除元素的情況。這時候大家都不會使用foreach迴圈的方式來遍歷List,因為它會拋java.util.ConcurrentModificationException異常。比如下面的程式碼就會拋這個異常:

12345678910111213List list=newArrayList();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");for(Stringitem:list){if(item.equals("3")){System
.out.println(item);list.remove(item);}}System.out.println(list.size());

那是不是在foreach迴圈時刪除元素一定會拋這個異常呢?答案是否定的。

見這個程式碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 List list=newArrayList(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); for(Stringitem:list){ if(item
.equals("4")){ System.out.println(item); list.remove(item); } } System.out.println(list.size());

這段程式碼和上面的程式碼只是把要刪除的元素的索引換成了4,這個程式碼就不會拋異常。為什麼呢?

接下來先就這個程式碼做幾個實驗,把要刪除的元素的索引號依次從1到5都試一遍,發現,除了刪除4之外,刪除其他元素都會拋異常。接著把list的元素個數增加到7試試,這時候可以發現規律是,只有刪除倒數第二個元素的時候不會丟擲異常,刪除其他元素都會丟擲異常。

好吧,規律知道了,可以從程式碼的角度來揭開謎底了。

首先java的foreach迴圈其實就是根據list物件建立一個Iterator迭代物件,用這個迭代物件來遍歷list,相當於list物件中元素的遍歷託管給了Iterator,你如果要對list進行增刪操作,都必須經過Iterator,否則Iterator遍歷時會亂,所以直接對list進行刪除時,Iterator會丟擲ConcurrentModificationException異常

其實,每次foreach迭代的時候都有兩部操作:

  1. iterator.hasNext()  //判斷是否有下個元素
  2. item = iterator.next()  //下個元素是什麼,並賦值給上面例子中的item變數

hasNext()方法的程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 publicEnext(){ checkForComodification(); try{ Enext=get(cursor); lastRet=cursor++; returnnext; }catch(IndexOutOfBoundsExceptione){ checkForComodification(); thrownewNoSuchElementException(); } } finalvoidcheckForComodification(){ if(modCount!=expectedModCount) thrownewConcurrentModificationException(); } }

這時候你會發現這個異常是在next方法的checkForComodification中丟擲的,丟擲原因是modCount != expectedModCount

  • modCount是指這個list物件從new出來到現在被修改次數,當呼叫List的add或者remove方法的時候,這個modCount都會自動增減;
  • expectedModCount是指Iterator現在期望這個list被修改的次數是多少次。

iterator建立的時候modCount被賦值給了expectedModCount,但是呼叫list的add和remove方法的時候不會同時自動增減expectedModCount,這樣就導致兩個count不相等,從而丟擲異常。

如果想讓其不丟擲異常,一個辦法是讓iterator在呼叫hasNext()方法的時候返回false,這樣就不會進到next()方法裡了。這裡cursor是指當前遍歷時下一個元素的索引號。比如刪除倒數第二個元素的時候,cursor指向最後一個元素的,而此時刪掉了倒數第二個元素後,cursor和size()正好相等了,所以hasNext()返回false,遍歷結束,這樣就成功的刪除了倒數第二個元素了。

破除迷信,foreach迴圈遍歷的時候不能刪除元素不是絕對,倒數第二個元素是可以安全刪除的~~(當然以上的思路都是建立在list沒有被多執行緒共享的情況下)