并发修改异常及解决方案, java.util.ConcurrentModificationException

2022/3/19 11:27:43

本文主要是介绍并发修改异常及解决方案, java.util.ConcurrentModificationException,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

并发修改异常简介

异常展示

异常原因

异常探究

异常流程

并发修改异常出现的意义

并发修改的解决方案

1.迭代器遍历,迭代器增删元素。

解决方案1:

集合遍历元素,集合增删元素

解决方案2


并发修改异常简介

  • 什么是并发修改异常

    • 并发的意思是同时发生,并发修改的意思是在同时发生某个时间时去修改。

    • 并发修改异常的原因是:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。

  • 场景:当我们在对集合进行迭代遍历操作的时候,如果同时对集合对象中的元素进行某些操作(增删),则容易导致并发修改异常的产生。

异常展示

  • 我们创建一个ArrayList数组,当数组中有“world”元素时,我们增加一个“javaee”元素

import java.util.ArrayList;
import java.util.Iterator;
​
public class TestDemo {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<>();
        //创建并添加元素
        array.add("hello");
        array.add("world");
        array.add("java");
​
        Iterator it = array.iterator();
        while (it.hasNext()){
            String s =(String) it.next();
            if ("world".equals(s)){
                array.add("javaee");
            }
        }
​
    }
}

报错原因:Exception in thread "main" java.util.ConcurrentModificationException (并发 修改异常)

异常原因

  • 通过控制台找到问题产生的根源

    Exception in thread "main" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)//问题根源
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
        at TestDemo.main(TestDemo.java:15)

  • 查看根源方法代码

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();//抛出并发异常
    }

  • 原因:由异常根源的方法内容得知,当一个变量modCouunt 不等于 expectedModCount变量时,会抛出此异常

异常探究

接下来探究modCount与expectedModCount具体代表什么

  • modCount

    • 在抽象类AbstractList定义了modCount变量,而作为他的子类ArrayList继承到了modCount这个变量

    • 源码中对于modCount的解释

    The number of times this list has been structurally modified
    意思为:modCount代表集合在结构上修改的次数
  • expectedModCount

    • 当ArrayList对象调用iteroter()方法时,会创建内部类Itr的对象

    • Itr的源码

    private class Itr implements Iterator<E> {
        int cursor;      
        int expectedModCount = modCount;    //
        ....
    }
    • 由此段代码可知:当ArrayList对象调用iteroter()方法时,会将expectedModCount的值会等于modCount变量的值。

  • modCount是如何变化的?

    • 当我们创建ArrayList对象的时候,ArrayList对象里包含了此变量modCount并且初始化值为0;

    • 通过查看源码,我们发现ArrayList中的添加删除操作的方法,都会使modCount自增一次,例如删除的源码

    public E remove(int index) {
        rangeCheck(index);
        modCount++;     //modCount自增
        ...//此处省略代码
    }
    • 每次都集合元素的个数产生变化时,modCount的值都会+1modCount变量就是记录了对集合元素个数的改变次数

异常流程

分析完modCount与expectedModCount两个变量,我们再来结合迭代器的工作流程来分析异常出现的过程

  • 迭代器的创建

    • 当ArrayList对象调用iteroter()方法时,会创建内部类Itr的对象,此时迭代器对象中有两个最关键的成员变量:cursor、expectedModCount

    private class Itr implements Iterator<E> {
        int cursor;       // 集合中元素的索引
        int expectedModCount = modCount;
    }
    • cursor

      • 迭代器工作就是将集合中的元素逐个取出,而cursor就是迭代器中用于指向集合中某个元素的指针

      • 在迭代器迭代的过程中,cursor初始值为0,每次取出一个元素,cursor值会+1,以便下一次能指向下一个元素,直到cursor值等于集合的长度为止,从而达到取出所有元素的效果。

    • expectedModCount

      • expectedModCount在迭代器对象创建时被赋值为modCount

      • 在迭代集合元素的过程中,迭代器通过检查expectedModCount和modCount的值是否相同,以防止出现并发修改。

  • 迭代器迭代过程

    • 在使用迭代器时,都会使用迭代器的hasNext()方法判断是否还有下一个元素,源码

    public boolean hasNext() {
        return cursor != size;
    }
    //cursor初始值是0,默认指向集合中第一个元素,每次取出一个元素,cursor值就会自增一次 //size是集合中的成员变量,用于表示集合的元素个数 
    //因为集合中最后一个元素的索引为size-1,只要cursor值不等于size那么就证明还有下一个元素,此时hasNext方法返回true,如若cursor值与size相等了,那么证明已经迭代完了最后一个元素,此方法返回false。
    ​
    • 当我们hasNext()返回true下一个元素仍有时,我们就会使用next()方法去取出元素,next()源码

    public E next() {
        checkForComodification();       //调用了最开始方法
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
    • 我们发现在使用next()方法时,第一行就调用了我们最开始异常根源的方法。

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

    所以我们最终得知:

    • 当迭代器通过next()方法返回元素之前都会检查集合中的modCount和最初赋值给迭代器的expectedModCount是否相等,如果不等,则抛出并发修改异常。

    • 也就说,当迭代器工作的过程中,不允许集合擅自修改集合结构,如果修改了会导致modCount值变化,从而不会等于expectedModCount,那么迭代器就会抛出并发修改异常

并发修改异常出现的意义

  • 我们通过上文的分析其实可以知道,迭代器是通过cursor指针指向对应集合元素来挨个获取集合中元素的,每次获取对应元素后cursor值+1指向下一个元素,直到集合最后一个元素。

  • 那么如果在迭代器获取元素的过程中,集合中元素的个数突然改变,那么下一次获取元素时,cursor能否正确的指向集合的下一个元素就变得未知了,这种不确定性有可能导致迭代器工作出现意想不到的问题。

  • 为了防止在将来某个时间任意发生不确定行为的风险,我们在使用迭代器的过程中不允许修改集合结构(也可以说是不允许修改元素个数),否则迭代器会抛出异常结束程序。

并发修改的解决方案

我们既然不能使用迭代器遍历的,集合修改元素的方式,我们可以

  • 1.迭代器遍历,迭代器增删元素。

    • 迭代器中提供了增删的方法,且对cursor进行了处理,Iterator迭代器却没有添加功能,所以我们使用其子接口ListIterator

    • 例如 获取Itr迭代器的子类对象ListItr,ListItr中有添加元素的add方法:

    public void add(E e) {
        checkForComodification();
    ​
        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;         //对cursor进行处理防止出现问题
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

  • 解决方案1:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
​
public class TestDemo {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<>();
        //创建并添加元素
        array.add("hello");
        array.add("world");
        array.add("java");
        //Iterator迭代器却没有添加功能,所以我们使用其子接口ListIterator
        ListIterator lit = array.listIterator();
        while(lit.hasNext()){
            String s = (String) lit.next();
            if("world".equals(s)){
                lit.add("javaee");
            }
        }
​
        System.out.println("array:"+array);
​
    }
}
  • 集合遍历元素,集合增删元素

    • 属于List体系的集合我们可以使用用普通for循环,通过索引获取集合元素的方法来遍历集合,这个时候修改集合结构是不会出现异常的。

  • 解决方案2

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
​
public class TestDemo {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<>();
        //创建并添加元素
        array.add("hello");
        array.add("world");
        array.add("java");
​
        for(int i = 0;i < array.size();i++){
            String element = array.get(i);
            if(element.equals("java")){
                array.add("javaee");
            }
        }
​
        System.out.println("array:"+array);
​
    }
}



这篇关于并发修改异常及解决方案, java.util.ConcurrentModificationException的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程