前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。
Java在操作ArrayList、HashMap、TreeMap等容器類時,遇到了java.util.ConcurrentModificationException異常。以ArrayList為例,如下面的代碼片段:
?
- import?java.util.ArrayList;??
- import?java.util.Iterator;??
- import?java.util.List;??
- import?java.util.concurrent.CopyOnWriteArrayList;??
- ??
- public?class?Test?{??
- ??
- ????public?static?void?main(String[]?args){??
- ????????List<String>?strList?=?new?ArrayList<String>();??
- ????????strList.add("string1");??
- ????????strList.add("string2");??
- ????????strList.add("string3");??
- ????????strList.add("string4");??
- ????????strList.add("string5");??
- ????????strList.add("string6");??
- ??????????
- ????????//?操作方式1:while(Iterator);報錯??
- ????????Iterator<String>?it?=?strList.iterator();??
- ????????while(it.hasNext())?{??
- ????????????String?s?=?it.next();??
- ????????????if("string2".equals(s))?{??
- ????????????????strList.remove(s);??
- ????????????}??
- ????????}??
- ??????????
- ????????//?解決方案1:使用Iterator的remove方法刪除元素??
- ????????//?操作方式1:while(Iterator):不報錯??
- //??????Iterator<String>?it?=?strList.iterator();??
- //??????while(it.hasNext())?{??
- //??????????String?s?=?it.next();??
- //??????????if("string2".equals(s))?{??
- //??????????????it.remove();??
- //??????????}??
- //??????}??
- ??????????
- ????????//?操作方式2:foreach(Iterator);報錯??
- //??????for(String?s?:?strList)?{??
- //??????????if("string2".equals(s))?{??
- //??????????????strList.remove(s);??
- //??????????}??
- //??????}??
- ??????????
- ????????//?解決方案2:不使用Iterator遍歷,注意索引的一致性??
- ????????//?操作方式3:for(非Iterator);不報錯;注意修改索引??
- //??????for(int?i=0;?i<strList.size();?i++)?{??
- //??????????String?s?=?strList.get(i);??
- //??????????if("string2".equals(s))?{??
- //??????????????strList.remove(s);??
- //??????????????strList.remove(i);??
- //??????????????i--;//?元素位置發生變化,修改i??
- //??????????}??
- //??????}??
- ??????????
- ????????//?解決方案3:新建一個臨時列表,暫存要刪除的元素,最后一起刪除??
- //??????List<String>?templist?=?new?ArrayList<String>();??
- //??????for?(String?s?:?strList)?{??
- //??????????if(s.equals("string2"))?{??
- //??????????????templist.add(s);??
- //??????????}??
- //??????}??
- //??????//?查看removeAll源碼,其使用Iterator進行遍歷??
- //??????strList.removeAll(templist);??
- ??????????
- ????????//?解決方案4:使用線程安全CopyOnWriteArrayList進行刪除操作??
- //??????List<String>?strList?=?new?CopyOnWriteArrayList<String>();??
- //??????strList.add("string1");??
- //??????strList.add("string2");??
- //??????strList.add("string3");??
- //??????strList.add("string4");??
- //??????strList.add("string5");??
- //??????strList.add("string6");??
- //??????Iterator<String>?it?=?strList.iterator();??
- //??????while?(it.hasNext())?{??
- //??????????String?s?=?it.next();??
- //???????????if?(s.equals("string2"))?{??
- //???????????????strList.remove(s);??
- //??????????}??
- //??????}??
- ??????????
- ????}?????
- }??
?
?
執行上述代碼后,報錯如下:
?
- Exception?in?thread?"main"?java.util.ConcurrentModificationException??
- ????at?java.util.ArrayList$Itr.checkForComodification(Unknown?Source)??
- ????at?java.util.ArrayList$Itr.next(Unknown?Source)??
- ????at?concurrentModificationException.Test.main(Test.java:21)??
在第21行報錯,即it.next(),迭代器在獲取下一個元素時報錯。找到java.util.ArrayList第830行,看到it.next()的源代碼,如下:
?
?
- @SuppressWarnings("unchecked")??
- public?E?next()?{??
- ????checkForComodification();??
- ????int?i?=?cursor;??
- ????if?(i?>=?size)??
- ????????throw?new?NoSuchElementException();??
- ????Object[]?elementData?=?ArrayList.this.elementData;??
- ????if?(i?>=?elementData.length)??
- ????????throw?new?ConcurrentModificationException();??
- ????cursor?=?i?+?1;??
- ????return?(E)?elementData[lastRet?=?i];??
- }??
調用了checkForComodification()方法,代碼如下:
?
?
- final?void?checkForComodification()?{??
- ????if?(modCount?!=?expectedModCount)??
- ????????throw?new?ConcurrentModificationException();??
- }??
即,比較modCount和expectedModCount兩個是否相等。modCount是ArrayList從AbstractList繼承來的屬性,查看modCount屬性的doc文檔,可知,modCount表示列表(list)被結構性修改(structurally modified)的次數。structurally modified是指造成列表中元素個數發生變化的操作,ArrayList中的add,addAll,remove, fastRemove,clear, removeRange, ?ensureCapacity, ?ensureCapacityInternal, ?ensureExplicitCapacity等方法都會使modCount加1,而batchRemove,removeAll,retainAll等方法則根據刪除的元素數增加modCount的值(removeAll和retainAll都是調用batchRemove實現,具體modCount的修改算法還需研究)。
?
第一個代碼片段中的操作方式1和操作方式2都是采用了迭代器的方式。當使用iterator方法得到Iterator對象(或者使用listIterator獲得ListItr對象),其實是返回了一個Iterator接口的實現類ArrayList$Itr(繼承自AbstractList$Itr),該類為ArrayList的一內部類,該類中有一個expectedModCount字段,當調用ArrayList$Itr的next方法時,會先檢查modCount的值是否等于expectedModCount的值(其實在調用next, remove, previous, set, add等方法時都會檢查),不相等時就會拋出java.util.ConcurrentModificationException異常。這種現象在Java?doc中稱作fail-fast。
為什么會拋出該異常呢?從代碼可以看到調用了6次add方法,這時modCount的值也就為6,當當使用iterator方法得到Iterator對象時把modCount的值賦給了expectedModCount,開始時expectedModCount與modCount是相等的,當迭代到第二個元素(index=1)“string2”時,因為if條件為true,于是又調用了remove方法,調用remove方法時modCount值又加1,此時modCount的值為7了,而expectedModCount的值并沒有改變,當再次調用ArrayList$Itr的next方法時檢測到modeCount與expectedModCount不相等了,于是拋出異常。
當把if語句寫成if(s.equals("string5"))時又沒有拋出該異常,這又是為什么呢?ArrayList$Itr中還有一個名為cursor的字段用來指向迭代時要操作的元素索引,初始值為0,每調用一次next方法該字段值加1,注意是先從集合中取出了元素再加1的。當判斷"string5"時,注意是倒數第二個元素,這些cursor的值為5,移除掉元素"string5"時,List的size為5,當調用ArrayList$Itr的hasNext方法判斷有無下一個元素時,判斷的依據為cursor的值與size是否相等,不相等則還有下一個元素,而此時兩者值剛好相等,也就沒有往下執行next方法了,也就沒有拋出異常,因此刪掉倒數第二個元素時不會拋異常的異常。
?
解決方案有四種,直接看第一段代碼即可。
?
參考鏈接:
http://blog.csdn.NET/xtayfjpk/article/details/8451178
《多線程情況下只能使用CopyOnWriteArrayList》http://www.2cto.com/kf/201403/286536.html
?