本文目錄
一、錯誤描述
ArrayList是java開發時經常使用的一個類,又經常碰到需要對ArrayList循環刪除元素的情況。這時候大家都不會使用foreach循環的方式來遍歷List,因爲它會拋java.util.ConcurrentModificationException異常。 Vector也同樣會報異常。比如下面的代碼:
public class ConcurrentTest {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("張三", 23));
list.add(new Person("李四", 24));
list.add(new Person("王五", 25));
list.add(new Person("麻六", 26));
list.forEach(person -> {
if (person.getAge() == 24){
list.remove(person);
}
});
System.out.println(list);
}
}
拋出異常信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1252)
at com.uiotsoft.back.iotoperation.business.ConcurrentTest.main(ConcurrentTest.java:23)
二、錯誤原因
其實,基本上所有的集合類都會有一個叫做快速失敗的校驗機制,當一個集合在被多個線程修改並訪問時,就會出現ConcurrentModificationException 校驗機制。它的實現原理就是我們經常提到的modCount修改計數器。如果在讀列表時,modCount發生變化則會拋出ConcurrentModificationException異常。這與線程同步是兩碼事,線程同步是爲了保護集合中的數據不被髒讀、髒寫而設置的。
首先java的foreach循環其實就是根據list對象創建一個Iterator迭代對象,用這個迭代對象來遍歷list,相當於list對象中元素的遍歷託管給了Iterator,你如果要對list進行增刪操作,都必須經過Iterator。iterator創建的時候modCount被賦值給了expectedModCount,但是調用list的add和remove方法的時候不會同時自動增減expectedModCount,這樣就導致兩個count不相等,從而拋出異常。
三、解決方案
以下提供兩種解決方案:
3.1 方案一
使用CopyOnWriteArrayList<>(),例如如下例子:
public class ConcurrentTest {
public static void main(String[] args) {
List<Person> copyList = new CopyOnWriteArrayList<>();
copyList.add(new Person("張三", 23));
copyList.add(new Person("李四", 24));
copyList.add(new Person("王五", 25));
copyList.add(new Person("麻六", 26));
copyList.forEach(person -> {
if (person.getAge() == 25){
copyList.remove(person);
}
});
System.out.println(copyList);
}
}
結果正常:
3.2 方案二
使用增強for循環
public class ConcurrentTest {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("張三", 23));
list.add(new Person("李四", 24));
list.add(new Person("王五", 25));
list.add(new Person("麻六", 26));
for (Person person : list) {
if (person.getAge() == 25){
list.remove(person);
}
}
System.out.println(list);
}
}
結果正常,請看方案1的結果。