为什么简单的删除集合中的元素竟然报错了?
作者 | 七十一
來源 |?程序員巴士
前言
什么是快速失敗:fail-fast 機制是java集合(Collection)中的一種錯誤機制。它只能被用來檢測錯誤,因為JDK并不保證fail-fast機制一定會發(fā)生。當多個線程對同一個集合的內容進行操作時,就可能會產(chǎn)生fail-fast事件。
運行如下代碼,即可出現(xiàn)異常:
//?關于fail-fast的一些思考 public?class?FailFastTest?{public?static?void?main(String[]?args)?{//?構建ArrayListList<Integer>?list?=?new?ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);for?(int?i?:?list)?{list.remove(1);}} }控制臺會輸出如下異常:
為什么要報這個錯?途中出錯的地方是ArrayList中的代碼,定位到該處代碼:
final?void?checkForComodification()?{if?(modCount?!=?expectedModCount)throw?new?ConcurrentModificationException(); }modCount是這個集合修改的次數(shù),這個屬性來自AbstractList,而我們的ArrayList是繼承了該抽象類的。
protected?transient?int?modCount?=?0;expectedModCount又是啥呢?當我們進行遍歷時候debug一下發(fā)現(xiàn)進行forEach循環(huán)的時候其實走了下面這個方法iterator,而且遍歷這個底層還是走的hasNext方法
public?Iterator<E>?iterator()?{return?new?Itr();}判斷是否有下一個元素
public?boolean?hasNext()?{return?cursor?!=?size;}next()方法用于獲取元素
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];}點進這個new Itr(),驚喜的發(fā)現(xiàn)原來這個expectedModCount是在這里被賦值的而且和modCount一樣
private?class?Itr?implements?Iterator<E>?{int?cursor;???????//?index?of?next?element?to?returnint?lastRet?=?-1;?//?index?of?last?element?returned;?-1?if?no?suchint expectedModCount = modCount;?//?注意:此處進行賦值............接下來看下ArrayList的remove()方法,其對modCount進行了增加,這是導致報錯的原因
public?E?remove(int?index)?{rangeCheck(index);modCount++;?//?對modCount進行了++的操作E?oldValue?=?elementData(index);int?numMoved?=?size?-?index?-?1;if?(numMoved?>?0)System.arraycopy(elementData,?index+1,?elementData,?index,numMoved);elementData[--size]?=?null;?//?clear?to?let?GC?do?its?workreturn?oldValue;}上面的next()方法這有調用一個checkForComodification()方法,下面貼一下這方法的代碼
final?void?checkForComodification()?{if?(modCount?!=?expectedModCount)throw?new?ConcurrentModificationException();}ArrayList里面remove()方法進行了modCount++操作,原來是我們對集合進行操作后改變了modCount導致上面代碼成立,從而拋出異常
但是當我們使用Itr類的remove,也就是如下代碼進行對元素改動時,不會拋出ConcurrentModificationException異常
public?void?remove()?{if?(lastRet?<?0)throw?new?IllegalStateException();checkForComodification();try?{ArrayList.this.remove(lastRet);cursor?=?lastRet;lastRet?=?-1;//?將ArrayList的modCount賦值給Itr類的expectedModCount?//這樣再次調用next方法時就不會出現(xiàn)這倆個值不一致?從而避免報錯expectedModCount?=?modCount;?}?catch?(IndexOutOfBoundsException?ex)?{throw?new?ConcurrentModificationException();}}與ArrayList的remove()方法不同的是,該remove()方法調用ArrayList.this.remove(lastRet);后顯然modCount++了,但是馬上又讓expectedModCount = modCount就是這樣才不會拋出異常。
梳理整個流程:
1、for循環(huán)遍歷實質上調用的是Itr類的方法進行遍歷(Itr類實現(xiàn)了Iterator)
2、Itr類在構造的時候會將ArrayList的modCount(實際上modCount是AbstractList的屬性,但是ArrayList繼承了AbstractList)賦值給Itr類的expectedModCount
3、for循環(huán)中調用的remove()方法時ArrayList的,這個方法會對modCount進行++操作
4、remove方法調用后,繼續(xù)遍歷會調用Itr的next()方法,而這個next()方法中的checkForComodification()方法會對modCount和expectedModCount進行對比,由于remove方法已經(jīng)操作過modCount因此這倆個值不會相等,故報錯。
如何改進?
1、可以使用Itr中的remove方法進行改進,改進代碼如下
public?static?void?main(String[]?args)?{//?構建ArrayListList<Integer>?list?=?new?ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);Iterator<Integer>?iterator?=?list.iterator();while(iterator.hasNext())?{iterator.next();iterator.remove();}System.out.println(list.size());?//?0}2、使用CopyOnWriterArrayList來代替Arraylist,它對ArrayList的操作時會先復制一份數(shù)據(jù)出來操作完了再將其更新回去替換掉舊的,所以CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實時一致性。這是采用了CopyOnWriterArrayList的fail-safe機制,當集合的結構被改變的時候,fail-safe機制會在復制原集合的一份數(shù)據(jù)出來,然后在復制的那份數(shù)據(jù)遍歷,fail-safe機制,在JUC包的集合都是有這種機制實現(xiàn)的。
雖然fail-safe不會拋出異常,但存在以下缺點
1、復制時需要額外的空間和時間上的開銷。
2、不能保證遍歷的是最新內容。
總結
對于fail-fast機制,我們要操作List集合時可以使用Iterator的remove()方法在遍歷過程中刪除元素,或者使用fail-safe機制的CopyOnWriterArrayList,當然使用的時候需要權衡下利弊,結合相關業(yè)務場景。
往期推薦
低代碼發(fā)展專訪系列之八:低代碼平臺能夠打破企業(yè)「應用孤島」現(xiàn)象嗎?
Medusa又一個開源的替代品
用了HTTPS,沒想到還是被監(jiān)控了
快速搭建實驗環(huán)境:使用 Terraform 部署 Proxmox 虛擬機
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的为什么简单的删除集合中的元素竟然报错了?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 好难啊……一个 try-catch 问出
- 下一篇: 2017,人工智能技术如何让中国开发者“