remove引起的concurrentmodificationexception异常

在Lab2 Poetic Walks实验中,remove()方法在使用两种实现方式后始终不能通过测试

  1. 未能遍历整个集合
  2. 抛出concurrentmodificationexception异常

这里分析记录抛出concurrentmodificationexception异常的原因

案例

第一种情况

foreach

第二种情况

Arraylist

测试结果

未通过test
有未删除的边
将第99行代码修改为:

assertTrue(graph.targets("c").containsKey("a"));

修改的目的是对后面的测试用例进行测试

测试结果如下:
在这里插入图片描述

两种实现方法有相同的Failure Trace,只是第四行中一个定位在第85行,一个定位在第87行(见案例)

分析

remove()方法在Graph<L>接口中的spec如下所示:

     /**
     * Remove a vertex from this graph; any edges to or from the vertex are
     * also removed.
     * 
     * @param vertex label of the vertex to remove
     * @return true if this graph included a vertex with the given label;
     *         otherwise false (and this graph is not modified)
     */
     public boolean remove(L vertex);

在ConcreteEdgesGraph类中,用String实例化实现Graph<String>接口,remove()方法从加权有向图中删除点vertex,以及与vertex有关的边,如果图中存在点vertex返回true,否则返回false。
从测试结果来看,a->c边未被删除,且当第二次删除a点时,抛出ConcurrentModificationException.
针对第二种情况,将for-each循环修改如下:

for (int i = 0; i < edges.size(); i++){
if (edges.get(i).getsource().equals(vertex) || edges.get(i).gettarget().equals(vertex))
    edges.remove(edges.get(i));
    }

修改后的测试通过了,这是因为for-each不支持在循环中进行添加删除操作,否则会报出java.util.ConcurrentModificationException异常,所以错误定位出现在第85行。
但此时,a->c边仍未被删除。

源码分析

先看看Arraylist中的remove方法

public boolean remove(Object o) {  
        if (o == null) {  
            for (int index = 0; index < size; index++)  
                if (elementData[index] == null) {  
                    fastRemove(index);  
                    return true;  
                }  
        } else {  
            for (int index = 0; index < size; index++)  
                if (o.equals(elementData[index])) {  
                    fastRemove(index);  
                    return true;  
                }  
        }  
        return false;  
    }  
      
    private void fastRemove(int index) {  
        modCount++; // 这里只增加了modCount的值  
        int numMoved = size - index - 1;  
        if (numMoved > 0)  
            System.arraycopy(elementData, index + 1, elementData, index,  
                    numMoved);  
        elementData[--size] = null;  
    }  

再看看ArrayList源码中迭代器的实现类

     /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
     public Iterator<E> iterator() {
        return new Itr();
     }
     /**
     * An optimized version of AbstractList.Itr
     */
     private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

     public boolean hasNext() {
            return cursor != size;
     }

     @SuppressWarnings("unchecked")
        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];
        }

      public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;//关键:重置expectedModCount为modCount
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
              }
       }

注意,以下是异常的抛出机制:

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

在next方法中首先调用checkForComodification() 方法,判断expectedModCount和modCount两值是否相等,不等于就抛出ConcurrentModificationException异常。
在Arraylist中的remove()方法,调用的fastRemove()方法只增加了modCount的值,因此第二种情況在执行nex()t方法时抛出异常,错误定位在第87行。
事实上,两个案例实现过程都使用了Arraylist中的remove()方法,不会调整expectedModCount值,因此在边的删除过程中,即遍历过程中(无论是使用foreach循环还是迭代器的循环)都触发了checkForComodification()方法,边未能完成遍历,因此a->c边并没有删除。


我们注意到,迭代器实现类中有自己的remove()方法,并且在实现过程中调整了expectedModCount的值,因此不会抛出异常。
所以我们将remove()方法修改如下,使用了迭代器的remove()方法,修改如下:

@Override
 public boolean remove(String vertex) {
  Iterator<Edge> iter = edges.iterator();
  while (iter.hasNext()) {
   Edge edge = iter.next();
   if (edge.getsource().equals(vertex) || edge.gettarget().equals(vertex))
    iter.remove();
  }
  if (vertices.contains(vertex)) {
   vertices.remove(vertex);
   return true;
  }
  return false;
 }

修改后,测试也就通过了。
在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值