当前位置: 代码迷 >> 综合 >> 一文学会如何在开发中避免java.util.ConcurrentModificationException
  详细解决方案

一文学会如何在开发中避免java.util.ConcurrentModificationException

热度:99   发布时间:2023-11-24 01:13:34.0

error log

java.util.ConcurrentModificationException: nullat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) ~[na:1.8.0_201]at java.util.ArrayList$Itr.next(ArrayList.java:859) ~[na:1.8.0_201]

在使用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.ConcurrentModificationExceptionat 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

      • 在多线程环境中避免`ConcurrentModificationException`
      • 在单线程环境中避免`ConcurrentModificationException`
      • 使用for循环避免`ConcurrentModificationException`

在多线程环境中避免ConcurrentModificationException

1.将列表转换为数组,然后在数组上迭代。适用于小型列表,如果是列表很大,将会对性能产生很大影响。
2.将列表放在同步块中,来锁定列表。不建议使用,违背了多线程的宗旨。
3.JDK1.5或更高版本,可使用ConcurrentHashMapCopyOnWriteArrayList并发集合类。建议使用此方法来避免ConcurrentModificationException

在单线程环境中避免ConcurrentModificationException

可以使用Iteratorremove()函数,从列表中删除当前对象。

下面是使用并发集合类的例子:

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.ConcurrentModificationExceptionat 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)

查看ArrayListsubList的源码文档就能知道,除了允许subList返回的列表结构发生改变,其他任何方法首先都会检查源列表的modCount是否等于期望值,如果不是,则抛出ConcurrentModificationException

From Links

  相关解决方案