【Java笔记】以并发修改异常为例总结的出错解决办法
【java笔记】通过处理并发修改异常学到的错误处理办法。

先来看出错代码:

  1. /*需求:
  2. 遍历已有集合
  3. 如果在集合中发现存在字符串元素“world”
  4. 则在“world”后添加元素“javaee”
  5. */
  6. List list = new ArrayList();
  7. //多态的形式创建接口实现类对象
  8. list.add("helllo");
  9. list.add("java");
  10. list.add("world");
  11. //生成迭代器并判断有无world这个元素,如果有则向集合中添加“javaee”
  12. Iterator it = list.iterator();
  13. while(it.hasNext()){
  14. String s = it.next();
  15. if(s.equals("world")){
  16. list.add("javaee");
  17. }
  18. }
  19. System.out.println(list);
  20. //抛出异常:ConcurrentModificationException

 

这段代码中我试图在迭代的过程中通过list(List实现类对象)调用add方法向集合中添加元素并进行输出,但编译器在输出阶段抛出异常并终止了程序运行。

错误信息如下:

下面开始分析问题并找到解决方案:

        错误信息中蓝色高亮的部分UseIterator.java:23表示的是抛出异常的方法所在行数,进一步跟进是ArrayList中的Itr内部类中调用的next方法出的问题,再进一步跟进是ArrayList中的next方法调用的checkForComodification抛出的异常。

        抛出异常的代码如下:

  1. List<String> list = new ArrayList<String>();//创建集合对象
  2. Iterator<String> it = list.iterator();//创建迭代器实现类对象
  3. String s = it.next();//抛出异常的代码

从上面的代码中可以看到万恶之源是这个List,所以,先要有一个List,为了看起来更简洁这里我只拿了用到的内容过来,其他在这个案例中没有用到的就暂时没有管,下面的源码也会像这样简洁的拿过来看。

  1. public interface List<E> extends Collection<E> {
  2. Iterator<E> iterator();
  3. boolean add(E e);
  4. }

因为list是以多态的形式创建的ArrayList对象,所以也把ArrayList拿过来看看。

  1. public class ArrayList<E> extends AbstractList<E> implements List<E>{
  2. private class Itr implements Iterator<E> {
  3. int expectedModCount = modCount;
  4. /*
  5. expectedModCount:预期修改值
  6. modCount:实际修改值(继承自父类AbstractList)
  7. */
  8. @SuppressWarnings("unchecked")
  9. //异常初步定位在内部类Itr的next方法中
  10. public E next() {
  11. checkForComodification();//异常根源checkForComodification()方法
  12. int i = cursor;
  13. if (i >= size)
  14. throw new NoSuchElementException();
  15. Object[] elementData = ArrayList.this.elementData;
  16. if (i >= elementData.length)
  17. throw new ConcurrentModificationException();
  18. cursor = i + 1;
  19. return (E) elementData[lastRet = i];
  20. }
  21. //异常根源
  22. final void checkForComodification() {
  23. if (modCount != expectedModCount)
  24. //当预期修改值与实际修改值不同抛出异常
  25. throw new ConcurrentModificationException();
  26. }
  27. }
  28. }

从定义中可以看到ArrayList继承了AbstractList<E>,Abstract也暂时先把定义拿过来,里面需要用到的内容后面进行完善。

  1. public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>();

  ArrayList的定义中可以看出,除了继承AbstractList<E>外,还实现了接口List<E>,所以ArrayList中应该要有add方法的实现,所以将add方法的实现也拿过来看看。

  1. public class ArrayList<E> extends AbstractList<E> implements List<E>{
  2. //重写接口中的add方法
  3. public boolean add(E e) {
  4. modCount++;
  5. add(e, elementData, size);
  6. return true;
  7. }
  8. private class Itr implements Iterator<E> {
  9. int expectedModCount = modCount;
  10. /*
  11. expectedModCount:预期修改值
  12. modCount:实际修改值(继承自父类AbstractList)
  13. */
  14. @SuppressWarnings("unchecked")
  15. //异常初步定位在内部类Itr的next方法中
  16. public E next() {
  17. checkForComodification();
  18. //异常根源checkForComodification()方法
  19. ...
  20. }
  21. //异常根源
  22. final void checkForComodification() {
  23. if (modCount != expectedModCount)
  24. //当预期修改值与实际修改值不同抛出该异常
  25. throw new ConcurrentModificationException();
  26. }
  27. }
  28. }

其实到这一步基本上已经能看出来个大概了,Itr实现了List中的Iterator接口,而出错的代码中的it正是Iterator的实现类对象,这里的Itr就是这个实现类。

        Itr在实现next时调用了checkForComodification()方法将expectedModCount和modCount进行对比,如果两边不等就会抛出异常。那么现在要解决的就是expectedModCount和modCount是什么的问题了。

        在Itr中有一个将modCount的值赋值给expectedModCount的操作,其中expectedModCount是itr中的成员变量,但是在ArrayList和Itr中都没有modCount的声明,那就只能是一种可能:modCount继承自父类。所以现在可以完善AbstractList<E>的一部分了

  1. public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{
  2. protected transient int modCount = 0;
  3. //实际修改值最初值为0
  4. }

好了,到这里就破案了,最开始的时候expectedModCount和modCount的值都是0,但是在代码中我在迭代过程中通过list调用了一下add方法,但是在add方法中每add一次就会对modCount进行一次++,所以导致expectedModCount != modCount,最终抛出ConcurrentModificationException并发修改异常。

  1. //ArrayList重写接口中的add方法
  2. public boolean add(E e) {
  3. modCount++;
  4. //每次在集合中添加新的元素后实际修改值++
  5. add(e, elementData, size);
  6. return true;
  7. }

既然现在找到了错误的源头,那就要想一个解决方案出来了,最终目的是查找是否有“world”这个元素,并向集合中添加元素。迭代器行不通就换一种方法进行迭代,例如for循环

  1. for(int i = 0;i<list.size();++i){
  2. String s = list.get(i);//通过get获取元素
  3. if(s.equals("world")){
  4. list.add("javaee");
  5. }
  6. }

但是这里又有一个问题,通过get获取元素为什么就不会报错了?空口无凭还是来看看代码吧,前面已经分析过的部分就不在赘述了,直接拿过来搞里头:

  1. public interface List<E> extends Collection<E> {
  2. boolean add(E e);
  3. E get(int index);
  4. }
  5. public class ArrayList<E> extends AbstractList<E>implements List<E>{
  6. //实现List接口中的get,add方法
  7. public E get(int index) {
  8. Objects.checkIndex(index, size);
  9. return elementData(index);
  10. }
  11. public boolean add(E e) {
  12. modCount++;
  13. add(e, elementData, size);
  14. return true;
  15. }
  16. }

源码说明一切,可以看到ArrayList实现get方法时没有比较expectedModCount和modCount的操作,所以不管add中对modCount的值++多少次都没有影响。

下面再捋一下思路。

  1. 编译器抛出运行时异常(ConcurrentModificationException)
  2.  追溯异常出现的方法(Itr.next().checkForComodification())
  3.  跟进代码找出问题根源。
  4. 找出解决方案:采用for循环调用list.get()的方式遍历集合则可以避开checkForComodification()中的if (modCount != expectedModCount)比较,此时再调用add向集合添加元素即可。

        其中跟进代码思路如下:

  • 错误代码中通过list.iterator获取到元素迭代器,并通过循环调用next的方式实现遍历集合,当发现“world”时会执行add操作
  • ArrayList实现List中的add方法时加入了modCount++的操作,所以当向集合中添加元素后,modCount的值会发生变化
  • next方法调用了checkForComodification()方法,该方法中进行了if (modCount != expectedModCount)比较
  • 因为上一循环进行了add操作此时modCount值已经发生变化,再次进行比较时modCount != expectedModCount,所以抛出异常 

         大概就这些,欢迎指正。

版权声明:本文为haystack原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/haystack/p/15160288.html