遍历并批量删除容器中元素出现ConcurrentModificationException原因及处置
在以下四種遍歷過程中,前兩種會(huì)拋出ConcurrentModificationException,而后兩種方法是正確的.
Department類:
import?java.util.ArrayList;
import?java.util.Iterator;
import?java.util.List;
public?class?Department?{
????private?String?name;
????private?List<Member>?memberSheet;
????public?Department(String?name)?{
????????this.name?=?name;
????}
????public?void?addMemer(Member?member)?{
????????if?(memberSheet?==?null)?{
????????????memberSheet?=?new?ArrayList<Member>();
????????}
????????memberSheet.add(member);
????}
????public?void?printMemberSheet()?{
????????System.out.println("----部門"?+?name?+?"人員名單---");
????????for?(Member?member?:?memberSheet)?{
????????????System.out.println(member);
????????}
????}
????/**
?????*?里面的四個(gè)清除過程請分別獨(dú)立執(zhí)行
?????*
?????*/
????public?void?removeYoungerFromMemberSheet()?{????
????????//遍歷一:這個(gè)處理會(huì)拋出java.util.ConcurrentModificationException
????????for?(Member?member?:?memberSheet)?{
????????????if?(member.getAge()?<?30)?{
????????????????memberSheet.remove(member);
????????????}
????????}
????????
????????//遍歷二:這個(gè)處理也會(huì)拋出java.util.ConcurrentModificationException
????????for?(Iterator?it?=?memberSheet.iterator();?it.hasNext();)?{
????????????Member?member?=?(Member)?it.next();
????????????if?(member.getAge()?<?30)?{
????????????????memberSheet.remove(member);
????????????}
????????}
????????
????????//遍歷三:這個(gè)處理調(diào)用Iterator?本身的方法?remove(),會(huì)正常執(zhí)行
????????for?(Iterator?it?=?memberSheet.iterator();?it.hasNext();)?{
????????????Member?member?=?(Member)?it.next();
????????????if?(member.getAge()?<?30)?{
????????????????it.remove();
????????????}
????????}
????????
????????//遍歷四:這個(gè)處理不依賴Iterator,也會(huì)正常執(zhí)行
? ? ?for (int i = membeerSheet.size() - 1;i >= 0; i--) {?
Member member = memberSheet.get(i);?
if (member.getAge() < 30) {?
memberSheet.remove(member);?
}?
????}
????public?String?toString()?{
????????return?name;
????}
????public?String?getName()?{
????????return?name;
????}
????public?static?void?main(String[]?args)?{
????????Department?resarchDept?=?new?Department("研發(fā)部門");
????????resarchDept.addMemer(new?Member("張三",?38));
????????resarchDept.addMemer(new?Member("李四",?24));
????????resarchDept.addMemer(new?Member("王五",?30));
????????resarchDept.addMemer(new?Member("錢七",?22));
????????resarchDept.addMemer(new?Member("孫八",?39));
????????resarchDept.addMemer(new?Member("周九",?30));
????????resarchDept.removeYoungerFromMemberSheet();
????????resarchDept.printMemberSheet();
????}
}
Member類:
public?class?Member{
????private?String?name;
????private?int?age;
????private?Department?department;
????
????public?Member(String?name,int?age){
????????this.name=name;
????????this.age=age;
????}
????
????public?String?toString(){
????????StringBuffer?sb=new?StringBuffer();
????????sb.append("員工名="+name);
????????sb.append("?年齡="+age);
????????
????????if(department!=null){
????????????sb.append("?所屬部門="+department);
????????}
????????
????????return?sb.toString();
????}
????
????public?void?changeNewDepartment(Department?newDepartment)?{
????????department.removeMemer(name);
????????newDepartment.addMemer(this);
????}
????
????public?String?getName()?{
????????return?name;
????}
????public?void?setDepartment(Department?department)?{
????????this.department?=?department;
????}
}
?
同樣使用for(Object o:list)遍歷時(shí),對(duì)遍歷出來的對(duì)象進(jìn)行add/remove都會(huì)出現(xiàn)ConcurrentModificationException。
由于含有?foreach?循環(huán),對(duì)于?List?來說?foreach?循環(huán)編譯之后會(huì)變成類似于這樣的代碼:
for(Iterator<E>?i?=?list.iterator();?i.hasNext();?);
這里使用到了迭代器,對(duì)于?Vector?來說,iterator?方法是使用父類?AbstractList?中的,迭代器并沒有同步,因此是線程不安全的,在迭代的時(shí)候如果集合數(shù)量有增加或者減少都會(huì)即可感知到,并拋出?ConcurrentModificationException?異常。
對(duì)于?Collections?的?synchronizedCollection?或者?synchronizedList?方法包裝過的集合來說,對(duì) 于?iterator()?方法是需要用戶手工進(jìn)行同步的,詳見?Collections.synchronizedCollection?方法 的?API?文檔。
為什么會(huì)發(fā)生這樣的結(jié)果呢?這是因?yàn)?br />
"當(dāng)使用 fail-fast iterator 對(duì) Collection 或 Map 進(jìn)行迭代操作過程中嘗試直接修改 Collection / Map 的內(nèi)容時(shí),即使是在單線程下運(yùn)行, java.util.ConcurrentModificationException 異常也將被拋出。
Iterator 是工作在一個(gè)獨(dú)立的線程中,并且擁有一個(gè) mutex 鎖。 Iterator 被創(chuàng)建之后會(huì)建立一個(gè)指向原來對(duì)象的單鏈索引表,當(dāng)原來的對(duì)象數(shù)量發(fā)生變化時(shí),這個(gè)索引表的內(nèi)容不會(huì)同步改變,所以當(dāng)索引指針往后移動(dòng)的時(shí)候就找不到要迭代的對(duì)象,所以按照 fail-fast 原則 Iterator 會(huì)馬上拋出 java.util.ConcurrentModificationException 異常。
所以 Iterator 在工作的時(shí)候是不允許被迭代的對(duì)象被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除對(duì)象, Iterator.remove() 方法會(huì)在刪除當(dāng)前迭代對(duì)象的同時(shí)維護(hù)索引的一致性。"
java.util包中很多迭代器都是所謂的fail-fast迭代器.這些迭代器如果發(fā)現(xiàn)集合被修改,而且不是通過迭代器本身,那么拋出一個(gè)異常進(jìn)行清除-ConcurrentModificationException-從而避免不安全行為的發(fā)生.
在單線程中,解決這個(gè)問題:
1、在單線程代碼中如果通過Iterator.remove來刪除時(shí),不會(huì)拋出ConcurrentModificationException,如果直接從容器中刪除的話就會(huì)拋出了。
2、在iterator迭代位置增加synchronized(容器);
因此,第三種采用it.remove();不會(huì)出現(xiàn)任何異常,而第四不依賴于Iterator而依賴于索引當(dāng)然更不會(huì)出現(xiàn)異常.但這只是在單線程環(huán)境,
如果是多線程環(huán)境下,還是會(huì)存在上述問題。
要在多線程環(huán)境中解決這一問題:
1、使用克隆容器,并在副本上進(jìn)行迭代。由于克隆過程中需要加鎖,存在顯著的性能開銷,這個(gè)方式取決于容器的大小,每個(gè)元素上執(zhí)行的操作,迭代操作相對(duì)于容器其他操作的調(diào)用頻率,以及在響應(yīng)時(shí)間和吞吐量等方面的要求;
2、就需要使用 java.util.concurrent?中的任何集合都是經(jīng)過精心設(shè)計(jì)的,無論迭代、增加、刪除都是線程而全的,而且在迭代時(shí)不會(huì)拋了?ConcurrentModificationException?的異常。ConcurrentHaspMap、CopyOnWriteArrayList。
ConcurrentHaspMap:同步容器類在執(zhí)行每個(gè)操作期間都持有一個(gè)鎖,與HashMap一樣,也是基于散列的Map,但它使用了一種完全不同的加鎖策略來提供更高的并發(fā)性和伸縮性。ConcurrentHashMap和其他并發(fā)容器類一起增強(qiáng)了同步容器類:它們提供的迭代器不會(huì)拋出ConcurrentModificationException,因此不需要再迭代過程中隊(duì)容器加鎖。
?CopyOnWriteArrayList:用于替代同步List,在某些情況下提供了更好的并發(fā)性能,并且在迭代期間不需要對(duì)容器進(jìn)行加鎖或復(fù)制。(雷似的,CopyOnWriteArraySet的作用是替代同步Set)。“寫入時(shí)復(fù)制(Copy-On—Write)“容器的線程安全性在于,只要正確的發(fā)布一個(gè)事實(shí)不可變的對(duì)象,那么在訪問該對(duì)象時(shí)就不再需要進(jìn)一步的同步。在每次修改時(shí),都會(huì)創(chuàng)建并重新發(fā)布一個(gè)新的容器副本,從而實(shí)現(xiàn)可變性。”寫入時(shí)復(fù)制“容器的迭代器保留一個(gè)指向底層基礎(chǔ)數(shù)組的引用。這個(gè)數(shù)組當(dāng)前位于迭代器的起始位置,由于它不會(huì)被修改,因此 在對(duì)其進(jìn)行同步時(shí)只需要確保數(shù)組內(nèi)容的可見性。因此,多個(gè)線程可以同時(shí)對(duì)這個(gè)容器進(jìn)行迭代,而不會(huì)彼此干擾或者與修改容器的線程相互干擾。
顯然,每當(dāng)修改容器時(shí)都會(huì)復(fù)制底層數(shù)組,這需要一定的開銷,特別是當(dāng)容器的規(guī)模較大時(shí),僅當(dāng)?shù)僮鬟h(yuǎn)遠(yuǎn)多于修改操作時(shí),才應(yīng)該用”寫入時(shí)復(fù)制“容器。這個(gè)準(zhǔn)則很好地描述了許多事件通知系統(tǒng):在分發(fā)通知時(shí)需要迭代已注冊監(jiān)聽器鏈表,并調(diào)用每一個(gè)監(jiān)聽器,在大多數(shù)情況下,注冊和注銷事件監(jiān)聽器的操作遠(yuǎn)少于接收事件通知的操作。
總結(jié)
以上是生活随笔為你收集整理的遍历并批量删除容器中元素出现ConcurrentModificationException原因及处置的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HBase设计与开发性能优化(转)
- 下一篇: foreach对集合的输出作用