`
Disney2002
  • 浏览: 53055 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

再遇 java.util.ConcurrentModificationException

阅读更多
今天又一次遇到java.util.ConcurrentModificationException的异常, 对一个Collection / Map进行遍历或者迭代遍历, 并删除一些符合条件的值时容易出现.

很早的时候自己直接使用的是遍历, 基本上每次都会出现这个问题,后经朋友点拨,这里应该用迭代遍历,就不会出现ConcurrentModificationException, 果然很久没出现了,不过今天又一次遇到这个问题,并且是在迭代遍历的情况下,于是决定彻底解决此问题,不能再次出现了,好好找找原因,弄个明白,下面部分内容参考自网上网友的帖子.

Iterator<MonthlyStatData> it = papers.iterator();
        while (it.hasNext()) {
            MonthlyStatData data = it.next();
            if (data.getValue == 0 ) {
                papers.remove(data);
            }
        }


上面的代码是说有个月统计的List, 里面有部分统计数据是0, 不想让出现,于是就迭代遍历,删除统计数据是0的数据. 但是上面的代码会导致ConcurrentModificationException, 正确的应该是:

Iterator<MonthlyStatData> it = papers.iterator();
        while (it.hasNext()) {
            MonthlyStatData data = it.next();
            if (data.getValue == 0 ) {
                it.remove();
            }
        }


我们从代码中分析问题:
1.Iterator接口
public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

2.Collection接口
public interface Collection<E> extends Iterable<E> {
    ...
    Iterator<E> iterator();
    boolean add(E o);
    boolean remove(Object o);
    ...
}

上面两个接口中都含有remove方法, 接着我们看AbstractList
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { 
    protected transient int modCount = 0;
    private class Itr implements Iterator<E> {
        int cursor = 0;
        int lastRet = -1;
        int expectedModCount = modCount;

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

        public E next() {
            checkForComodification();  //特别注意这个方法
            try {
                E next = get(cursor);
                lastRet = cursor++;
                return next;
            } catch(IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet == -1)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);  //执行remove对象的操作
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;  //重新设置了expectedModCount的值,避免了ConcurrentModificationException的产生
            } catch(IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)  //当expectedModCount和modCount不相等时,就抛出ConcurrentModificationException
                throw new ConcurrentModificationException();
        }
    }   
}

接着看看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 remove method that skips bounds checking and does not
     * return the value removed.
     */
    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; // Let gc do its work
    }


看上面的代码,可以了解到产生ConcurrentModificationException的原因就是:
执行remove(Object o)方法之后,modCount和expectedModCount不相等了。然后当代码执行到next()方法时,判断了checkForComodification(),发现两个数值不等,就抛出了该Exception, 要避免这个Exception,就应该使用remove()方法。

转帖另外一个关于此问题描述:ConcurrentModificationException主要原因及处理方法
引用

当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时,即使是在单线程下运行,   java.util.ConcurrentModificationException 异常也将被抛出。

  Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

  所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

  有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

1 import java.util.*;
2
3 public final class MyTest
4 {
5     private static HashMap p_mapList = new HashMap(2);
6     private MyTest(){}
7     public static void init(){
8         // If only there are more than one element in Map,
9         // the ConcurrentModificationException will not be
10         // thrown.
11          p_mapList.put(new String("hello"),new String("world"));
12          p_mapList.put(new String("goto"),new String("hell"));
13      }
14     public static void clear() throws Exception{
15          Iterator pTmpKeys = null;
16          Long pTmpKeyLong;
17          pTmpKeys = p_mapList.keySet().iterator();
18          String pCurKey = null;
19          String pCurObj = null;
20         while(pTmpKeys.hasNext()){
21              pCurKey = (String) pTmpKeys.next();
22              pCurObj = (String) p_mapList.get(pCurKey);
23
24              p_mapList.put(pCurKey,null);
25             // You can not remove element in Map object directly.
26             //p_mapList.remove(pCurKey);
27             // But you can remove current element by iterator itself.
28              pTmpKeys.remove();
29
30              System.out.println(pCurKey + " removed.");
31          }
32          System.out.println(p_mapList.size() + 
33                             " entries left after iterator.");
34          pTmpKeys = null;
35      }
36     public static void main(String[] args)
37                                  throws Exception{
38          MyTest.init();
39          MyTest.clear();
40      }
41 } 


还有一个Sun社区的对Iterator的深入分析:

引用

http://gceclub.sun.com.cn/yuanchuang/week-14/iterator.html
0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics