//迭代featureMap(HashMap对象)
for (Map.Entry<String, WordInfo> entry : featureMap.entrySet()) {
String key = entry.getKey();
WordInfo value = entry.getValue();
//其他操作
featureMap.remove(key);//抛出异常
}
这里应用的是for-each循环,是基于Iterator实现的。上述代码会出现java.util.ConcurrentModificationException异常。
关于ConcurrentModificationException异常,API中的叙述如下:
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
例如,某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。通常在这些情况下,迭代的结果是不确定的。如果检测到这种行为,一些迭代器实现(包括 JRE 提供的所有通用 collection 实现)可能选择抛出此异常。执行该操作的迭代器称为快速失败迭代器(fail-fast iterator),因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。
注意,此异常不会始终指出对象已经由不同 线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常。例如,如果线程使用快速失败迭代器在 collection 上迭代时直接修改该 collection,则迭代器将抛出此异常。
原因分析:
当使用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.( 为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。)
此程序最后的解决方法是:
Iterator<Map.Entry<String, WordInfo>> iter = featureMap.entrySet()
.iterator();
while (iter.hasNext()) {
Map.Entry<String, WordInfo> keyword = iter.next();
String key = keyword.getKey();
WordInfo value = keyword.getValue();
iter.remove();
}