记一次多线程遍历ArrayList,报java.util.ConcurrentModificationException异常
本次仅是多线程遍历,不涉及删改。摘记:
1、多线程环境选择CopyOnWriteArrayList、ConcurrentHashMap
2、CopyOnWriteArrayList只能用list.remove(), 不能使用Iterator.remove()进行删除。
解决方案出自CSDN文章。原文如下:
Java遍历 List 和 Map 出现 ConcurrentModificationException 异常原因分析及解决方法
一、单线程
异常情况举例
只要抛出出现异常,可以肯定的是代码一定有错误的地方。先来看看都有哪些情况会出现ConcurrentModificationException异常,下面以ArrayList的remove操作进行举例:
使用的数据集合:
List myList = new ArrayList();
myList.add( “1”); myList.add( “2”);
myList.add( “3”); myList.add( “4”);
以下三种情况都会出现异常:
Iterator it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove(value); // ConcurrentModificationException
}
}
for (Iterator it = myList.iterator(); it.hasNext();) {
String value = it.next();
if (value.equals( "3")) {
myList.remove(value); // ConcurrentModificationException
}
}
for (String value : myList) {
System. out.println( "List Value:" + value);
if (value.equals( "3")) {
myList.remove(value); // ConcurrentModificationException
}
}
异常信息如下:
Exception in thread “main” java.util.ConcurrentModificationException at java.util.AbstractList…
根本原因
以上都有3种出现异常的情况有一个共同的特点,都是使用Iterator进行遍历,且都是通过ArrayList.remove(Object) 进行删除操作。
想要找出根本原因,直接查看ArrayList源码,原因是modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。
List、Set、Map 都可以通过Iterator进行遍历,这里仅仅是通过List举例,在使用其他集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常。
解决方案
上面列举了会出现问题的几种情况,也分析了问题出现的根本原因,现在来总结一下怎样才是正确的,如果避免遍历时进行增删操作不会出现ConcurrentModificationException异常。
// 1 使用Iterator提供的remove方法,用于删除当前元素
Iterator it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
it.remove(); // ok
}
}
System. out.println( "List Value:" + myList.toString());
// 2 建一个集合,记录需要删除的元素,之后统一删除
List templist = new ArrayList();
for (String value : myList) {
if (value.equals( "3")) {
templist.add(value);
}
}
myList.removeAll(templist);
System.out.println( "List Value:" + myList.toString());
// 3. 使用线程安全CopyOnWriteArrayList进行删除操作
List myList = new CopyOnWriteArrayList();
Iterator it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove(value);
//it.remove();
// 注意CopyOnWriteArrayList不能用Iterator的remove方法,
否则UnsupportedOperationException异常;
//ArrayList不能直接用remove,只能用用Iterator的remove方法,
否则ConcurrentModificationException异常;
}
}
输出结果都是:List Value:[1, 2, 4] , 不会出现异常。
以上3种解决办法在单线程中测试完全没有问题,但是如果在多线程中呢?
二、多线程
同步异常情况举例
上面针对ConcurrentModificationException异常在单线程情况下提出了3种解决方案,但是如果涉及到多线程环境可能就不那么乐观了。
下面的例子中开启两个子线程,一个进行遍历,另外一个有条件删除元素:
List<String> list = new ArrayList<>();
list.add("1"); list.add("2"); list.add("3"); list.add("4");
Thread thread1 = new Thread() {
public void run() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread2 = new Thread() {
public void run() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if (value.equals("3")) {
iterator.remove();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread1.start();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size is " + list.size());
Exception in thread “Thread-0” java.util.ConcurrentModificationException at java.util.AbstractList…
结论:
上面的例子在多线程情况下,使用单线程的第1种解决方案iterator.remove()进行删除依然出现异常ConcurrentModificationException,测试得知使用单线程的第2种解决方案也会出现同样的问题。
接着来再看一下JavaDoc对java.util.ConcurrentModificationException异常的描述:
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
尝试方案
(1) 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
(2) 推荐使用ConcurrentHashMap(对应Map)或者CopyOnWriteArrayList(对应List)。
注意事项 :
CopyOnWriteArrayList只能用list.remove(), 不能使用Iterator.remove()进行删除。会出现如下异常:
java.lang.UnsupportedOperationException: Unsupported operation remove
at java.util.concurrent.CopyOnWriteArrayList
多线程解决方案如下
List<String> myList = new CopyOnWriteArrayList<>();
myList.add("1"); myList.add("2");
myList.add("3"); myList.add("4");
Thread thread1 = new Thread() {
public void run() {
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread2 = new Thread() {
public void run() {
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if (value.equals("3")) {
myList.remove(value);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread1.start();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size is " + myList.size());
#最后HashMap在多线程情况下:开启两个子线程,一个进行遍历,另外一个有条件删除元素报错代码,解决办法是使用ConcurrentHashMap,它是线程安全的,同一时刻只能有一个线程持有锁。
public static Map<String, String> maps = new HashMap<>();
public static void main(String[] args) {
Thread1 a1 = new Thread1();
new Thread(a1, "xc1").start();
Thread2 a2 = new Thread2();
new Thread(a2, "xc2").start();
}
static class Thread1 implements Runnable {
@Override
public void run() {
System.out.println("thread1 start");
for (int i = 0; i < 1000; i++) {
maps.put(String.valueOf(i), String.valueOf(i));
if (i == 99) {
System.out.println("thread1 start" + maps.size());
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
}
System.out.println("thread1 end");
}
}
static class Thread2 implements Runnable {
@Override
public void run() {
System.out.println("thread2 start");
Set<String> keys = maps.keySet();
Iterator<String> iter = maps.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
String value = maps.get(key);
if (value.equals("99")) {
System.out.println("thread2 start key" + keys.size());
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
}
System.out.println("thread2 end");
}
}
————————————————
版权声明:本文为CSDN博主「鸿蒙开发者」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33391863/article/details/107601831
光锥极客 2023-07-18 阅读量:1674