解决:ConcurrentModificationException
ConcurrentModificationException
在使用Java集合类时,java.util.ConcurrentModificationException
是一个非常常见的异常。fail-fast
机制是Java集合的一种错误机制。如果某个线程在迭代元素时,集合的结构发生了改变,此时iterator.next()
就会抛出ConcurrentModificationException
异常。
在多线程以及单线程Java编程环境中,可能会发生并发修改异常。
下面是个常见的例子:
public static void main(String args[]) {
List<String> demoList = new ArrayList<>();
demoList.add("1");
demoList.add("2");
demoList.add("3");
demoList.add("4");
demoList.add("5");
Iterator<String> it = demoList.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println("List Value:" + value);
if (value.equals("3"))
demoList.remove(value);
}
/*Map<String, String> demoMap = new HashMap<>();
demoMap.put("1", "1");
demoMap.put("2", "2");
demoMap.put("3", "3");
Iterator<String> mapIt = demoMap.keySet().iterator();
while (mapIt.hasNext()) {
String key = mapIt.next();
System.out.println("Map Value:" + demoMap.get(key));
if (key.equals("2")) {
//demoMap.put("1", "4");
demoMap.put("4", "4");
}
}*/
}
以上例子在执行时就会抛出java.util.ConcurrentModificationException
异常,控制台日志如下:
List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.example.demo.study.exception.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:21)
从日志信息中可以看出,当程序迭代器调用next()
函数时,抛出ConcurrentModificationException
异常。
通过跟踪源码可以发现,每次调用next()
函数前,都会调用checkForComodification()
来检查变量modCount
是否被修改。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
在多线程环境中避免ConcurrentModificationException
1.将列表转换为数组,然后在数组上迭代。适用于小型列表,如果是列表很大,将会对性能产生很大影响。
2.将列表放在同步块中,来锁定列表。不建议使用,违背了多线程的宗旨。
3.JDK1.5或更高版本,可使用ConcurrentHashMap
和CopyOnWriteArrayList
并发集合类。建议使用此方法来避免ConcurrentModificationException
。
在单线程环境中避免ConcurrentModificationException
可以使用Iterator
的remove()
函数,从列表中删除当前对象。
下面是使用并发集合类的例子:
public static void main(String args[]) {
List<String> demoList = new CopyOnWriteArrayList<>();
demoList.add("1");
demoList.add("2");
demoList.add("3");
demoList.add("4");
demoList.add("5");
Iterator<String> it = demoList.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println("demoList Value:" + value);
if (value.equals("2")) {
demoList.remove("4");
demoList.add("6");
demoList.add("7");
}
}
System.out.println("demoList :" + demoList);
System.out.println("demoList Size:" + demoList.size());
Map<String, String> demoMap = new ConcurrentHashMap<>();
demoMap.put("1", "1");
demoMap.put("2", "2");
demoMap.put("3", "3");
Iterator<String> mapIt = demoMap.keySet().iterator();
while (mapIt.hasNext()) {
String key = mapIt.next();
System.out.println("demoMap Value:" + demoMap.get(key));
if (key.equals("2")) {
demoMap.remove("3");
demoMap.put("4", "4");
demoMap.put("5", "5");
}
}
System.out.println("demoList :" + demoMap);
System.out.println("demoMap Size:" + demoMap.size());
}
运行结果如下:并没有抛出ConcurrentModificationException
异常。
demoList Value:1
demoList Value:2
demoList Value:3
demoList Value:4
demoList Value:5
demoList :[1, 2, 3, 5, 6, 7]
demoList Size:6
demoMap Value:1
demoMap Value:2
demoMap Value:null
demoMap Value:4
demoMap Value:5
demoList :{1=1, 2=2, 4=4, 5=5}
demoMap Size:4
使用for循环避免ConcurrentModificationException
如果是单线程环境中,需要在处理列表时添加额外对象,希望使用for
循环而不是Iterator
来实现。
for (int i = 0; i < demoList.size(); i++) {
System.out.println(demoList.get(i));
if (demoList.get(i).equals("3")) {
demoList.remove(i);
i--;
demoList.add("6");
}
}
注意,由于要删除的是同一对象,所以计数器要减1,如果其他的对象,则不需要计数器减1。自己尝试。
需要注意的一点
当使用subList的时修改了原始列表的结构,那么将会抛出ConcurrentModificationException
。
public static void main(String args[]) {
List<String> stores = new ArrayList<>();
stores.add("A001");
stores.add("B001");
stores.add("C001");
stores.add("D001");
List<String> first2Stores = stores.subList(0, 2);
System.out.println(stores + " , " + first2Stores);
stores.set(1, "B002");
System.out.println(stores + " , " + first2Stores);
stores.add("E001");
System.out.println(stores + " , " + first2Stores);//这里抛出异常
}
输出结果如下:
[A001, B001, C001, D001] , [A001, B001]
[A001, B002, C001, D001] , [A001, B002]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.example.demo.study.exception.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:102)
查看ArrayList
中subList
的源码文档就能知道,除了允许subList
返回的列表结构发生改变,其他任何方法首先都会检查源列表的modCount
是否等于期望值,如果不是,则抛出ConcurrentModificationException
。