蘇州哪家做網(wǎng)站便宜免費(fèi)創(chuàng)建網(wǎng)站軟件
一、異常產(chǎn)生
當(dāng)我們使用foreach迭代一個(gè)ArrayList或者HashMap時(shí),如果嘗試對(duì)集合做一些修改操作(例如刪除元素或新增),可能會(huì)拋出java.util.ConcurrentModificationException的異常。
```javapublic static void main(String[] args) {List<User> list=new ArrayList<>();for(int i=0;i<10;i++){User user = new User();user.setMsg("123"+i);user.setName("王總"+i);list.add(user);}list.forEach(item->{if(Objects.equals(item.getMsg(),"1234")){User user = new User();item.setName("456789");CglibUtil.copy(item,user);list.add(user);}});System.out.println(list);}
執(zhí)行之后會(huì)報(bào):
map的例子:```javajcItemMap.forEach((x,items)->{List<FinFreightItemR> finFreightItemRList = items.stream().filter(item -> Objects.equals(item.getAmountFlag(), FinConstant.YesOrNo.YES)).collect(Collectors.toList());if(CollectionUtil.isEmpty(finFreightItemRList)){jcItemMap.remove(x);allItemMap.remove(x);}});
二、java.util.ConcurrentModificationException異常產(chǎn)生的原因
ArrayList的父類AbstarctList中有一個(gè)域modCount,每次對(duì)集合進(jìn)行修改(增添元素,刪除元素。。。)時(shí)都會(huì)modCount++.而foreach的背后實(shí)現(xiàn)原理其實(shí)就是Iterator,等同于注釋部分代碼。在這里,迭代ArrayList的Iterator中有一個(gè)變量expectedModCount,該變量會(huì)初始化和modCount相等,但如果接下來(lái)對(duì)集合進(jìn)行修改,modCount改變,就會(huì)造成expectedModCount !=modCount,此時(shí)就會(huì)掏出異常java.util.ConcurrentModificationException異常。
過(guò)程如下圖:
三、異常的解決
1.單線程環(huán)境
上面我們已經(jīng)了解了異常的發(fā)送原因,接下我們說(shuō)一下解決方案。
1.1我們可以使用iterator迭代器進(jìn)行遍歷
Iterator<User> iterator = list.iterator();while(iterator.hasNext()){User user = iterator.next();if(Objects.equals(user.getMsg(),"1234")){iterator.remove();}}System.out.println(list);
細(xì)心的朋友會(huì)發(fā)現(xiàn)Itr中的也有一個(gè)remove方法,實(shí)質(zhì)也是調(diào)用了ArrayList中的remove,但增加了expectedModCount = modCount;保證了不會(huì)拋出java.util.ConcurrentModificationException異常。
但是,這個(gè)辦法的有兩個(gè)弊端
1.只能進(jìn)行remove操作,add、clear等Itr中沒(méi)有。
2.而且只適用單線程環(huán)境。
2、多線程環(huán)境
方法一:迭代前加鎖,解決了多線程問(wèn)題,但還是不能進(jìn)行迭代add、clear等操作。
public class Test12 {static List<String> list = new ArrayList<String>();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator<String> iterator = list.iterator();synchronized (list) {while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()+ ":" + iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};}.start();new Thread() {public synchronized void run() {Iterator<String> iterator = list.iterator();synchronized (list) {while (iterator.hasNext()) {String element = iterator.next();if (Objects.equals(element,"c")) {System.out.println(Thread.currentThread().getName()+ ":" + element);iterator.remove();}}}};}.start();}
}
方法二:采用CopyOnWriteArrayList,解決了多線程問(wèn)題,同時(shí)可以add、clear等操作
public class Test12 {static List<String> list = new CopyOnWriteArrayList<>();public static void main(String[] args) throws InterruptedException {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator<String> iterator = list.iterator();synchronized (list) {while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()+ ":" + iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};}.start();new Thread() {public synchronized void run() {Iterator<String> iterator = list.iterator();synchronized (list) {while (iterator.hasNext()) {String element = iterator.next();if (Objects.equals(element,"c")) {System.out.println(Thread.currentThread().getName()+ ":" + element);list.remove(element);list.add("123456");}}}};}.start();Thread.sleep(5000);System.out.println(list);}
}
CopyOnWriteArrayList也是一個(gè)線程安全的ArrayList,其實(shí)現(xiàn)原理在于,每次add或remove等所有的操作都是重新創(chuàng)建一個(gè)新的數(shù)組,再把引用指向新的數(shù)組。
對(duì)于HashMap的迭代刪除是一樣的