目录
并发修改异常简介
异常展示
异常原因
异常探究
异常流程
并发修改异常出现的意义
并发修改的解决方案
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的值都会+1,modCount变量就是记录了对集合元素个数的改变次数
分析完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能否正确的指向集合的下一个元素就变得未知了,这种不确定性有可能导致迭代器工作出现意想不到的问题。
为了防止在将来某个时间任意发生不确定行为的风险,我们在使用迭代器的过程中不允许修改集合结构(也可以说是不允许修改元素个数),否则迭代器会抛出异常结束程序。
我们既然不能使用迭代器遍历的,集合修改元素的方式,我们可以
迭代器中提供了增删的方法,且对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(); } }
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循环,通过索引获取集合元素的方法来遍历集合,这个时候修改集合结构是不会出现异常的。
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); } }