-
Concurrent Modification Exception - [java]
2009-11-17
Java Collections Framework,是开发者不可或缺的库,因为真的是太有用了~ 但是在使用中有一点是不可疏忽的,就是 ConcurrentModificationException,当你在迭代一个 collection 的时候,如果同时还不正确地修改了这个 collection (例如添加或删除元素),那么这个异常就会被抛出,因为这样做会让迭代器(Iterator)混乱。很多新手(包括我哈)在使用 collections 的时候都没有注意到这一点,于是程序在运行的时候就会出错了。
我们先看看这段代码:
Set<String> things = ...
...
for (String s : things) {
if (s.equals("trouble")) {
things.remove(s);
}
}这段代码一眼看上去没有什么问题,在 IDE 中也不会告诉你有什么不妥的地方。但是这段代码犯了我们刚才说的错误:在迭代的同时,如果找到了 "trouble" 这词,就会把它从这个 collection 里面删掉。这样做的话很容易产生 ConcurrentModificationException,然后你的程序就会挂掉~
解决方法1:使用 Iterator。
Iterator<String> it = things.iterator();
while (it.hasNext()) {
if (it.next().equals("trouble")) {
it.remove();
}
}这段代码的用途跟刚才的代码一模一样,但是不会产生 ConcurrentModificationException,因为当我们删除元素的时候使用的是迭代器的 remove() 方法,这是安全的。
Iterator 类中没有 add 方法,也就是说我们不能使用它来添加元素,Iterator 的子接口 ListIterator 中带有 add(),但是只有 List 类才能获得 ListIterator。那如果我们的 collection 不是 List,而我们又需要在迭代的时候添加元素呢?
解决方法2:把原来的 collection 复制一遍,用来迭代,原来的 collection 就用来添加或删除元素。
List<String> copyOfThings = new LinkedList<String>(things);
for (String s : copyOfThings) {
if (s.equals("trouble")) {
things.add("No ConcurrentModificationException!");
}
}在这段代码中,我们使用 LinkedList 把原来的 collection 复制了一遍,用来迭代,然后当我们需要添加元素的时候,我们使用的是原来的 collection,这样问题就解决了~ 因为我们只用复制的 collection 来迭代而不去修改,然后用原来的 collection 来修改而不去迭代。
复制版的 collection 我用的是 LinkedList 类,这样的话,原来的 collection 的迭代顺序就得到了保留,因为我们也许并不知道原来的 collection 是什么类,可能是 HashSet,也可能是 TreeSet,用 LinkedList (或者别的 Linked* 类) 来复制就不用担心复制以后迭代的顺序不一样了。
多线程:
如果你的 collection 会用在多线程的环境下,那么线程一在迭代的时候,线程二把它修改了,那么异常同样会被抛出。你可以使用 java.util.Collections 类中的某些方法把你的 collection 转换成线程安全的,具体的使用方法请看相应的 Javadoc。但是最佳的解决方法,就是不要把程序设计成这样,因为由多线程所产生的臭虫是很难搞定的~迭代的时候调用别的 method:
在迭代中调用别的 method 的时候要小心,确定你的 collection 不会被这些 method 所修改。就算现在没有被修改,将来别人也有可能会把这些 method 改掉,在其中添加或删除 collection 的元素,那么异常会再度出现哦~;-)
