ConcurrentModificationException主要原因及处理方法
2007年04月18日 星期三 12:57
当使用 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.
把HashMap改成ConcurrentHashMap
也可以
1
public class MapIterator {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
begin();
}
public static void begin()
{
ConcurrentHashMap hm2
=new ConcurrentHashMap();
HashMap<Integer,String> hm=new HashMap<Integer,String>();
char[] c=new char[1];
c[0]='a';
for(int i=0;i<10;i++)
{
c[0]+=i;
hm.put(new Integer(i),new String(c));
}
Set s=hm.entrySet();
Iterator i=s.iterator();
Map.Entry<Integer,String> me;
while(i.hasNext())
{
me=(Map.Entry<Integer,String>)i.next();
//System.out.println(me.getKey());
System.out.println(hm.size());
hm.remove(me.getKey());
}
System.out.println(hm.size());
}
}
先用HashMap进行操作!!
爆异常
java.util.HashMap$EntryIterator.next-----(积累----可以通过报异常 查看调用的什么方法!!)
(这里就是访问的HashMap内部类的EntryIterator)
看下源码
Form HashIterator
public void remove()
{
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
expectedModCount是为实现fail-fast实现的变量!
如果直接用HashMap中的方法删除的话 会导致两个变量不相等!!
看下ConcurrenthashMap中的Iterator实现
public void remove()
{
if (lastReturned == null)
throw new IllegalStateException();
ConcurrentHashMap.this.remove(lastReturned.key);
lastReturned = null;
}
上面来自类 ConcurrentHashMap内部类
private abstract class HashIterator<E> implements Iterator<E>
可以看出
直接调用ConcurrentHashmap的removal方法!!
所以也没有研究的必要了
下面是 fail-fast的介绍
An iterator is considered fail-fast if it throws a ConcurrentModificationException under either of the following two conditions:
1.In multithreaded processing: if one thread is trying to modify a Collection while another thread is iterating over it.
2.In single-threaded or in multithreaded processing: if after the creation of the Iterator, the container is modified at any time by any method other than the Iterator's own remove or add methods.
Note in particular what is implied by the second condition: After we create a container's iterator, during a loop that iterates over the container we must only use the remove (and when applicable add) methods defined for the iterator and that we must NOT use the same methods defined for the container itself. To illustrate this point, suppose we declare and initialize a List in the following manner
List list = new ArrayList();
list.add("Peter");
list.add("Paul");
list.add("Mary");
Let's say we wish to iterate over this list. We'd need to declare a ListIterator as follows:
ListIterator iter = list.listIterator();
Having created this iterator, we could now set up a loop like:
while(iter1.hasNext()){
String str = iter1.next();
// do something with str
}
Because iter is fail-fast, we are not allowed to invoke List's add or remove methods inside the loop. Inside the loop, we are only allowed to use ListIterator's add and remove methods. This makes sense because it is the Iterator object that knows where it is in a List as the List is being scanned. The List object itself would have no idea of that.
The Iterators supported by all the work-horse container classes, such as ArrayList, LinkedList, TreeSet, and HashSet, are fail-fast. The Iterator type retrofitted to the older container class Vector is also fail-fast. For associative containers, such as HashMap and the older HashTable, the Iterator type for the Collections corresponding to either the keys or the values or the <key, value> pairs are fail-fast with respect to the container itself. That means that even if you are iterating over, say, just the keys of the container, any illegal concurrent modifications to the underlying container would be detected.
One final note regarding iterators versus enumerations: It is also possible to use an Enumeration object returned by the elements() method for iterating over the older container types such as Vector. However, Enumerations do not provide a fail-fast method. On the other hand, the more modern Iterator returned by a Vector's iterator() and listIterator() methods are fail-fast. Hence, iterators are recommended over enumerations for iterating over the elements of the older container types.
继承自AbstractList的List:LinkedList,ArrayList,Vector,Stack的Iterator都有这种属性
PriorityQueue的Iterator也有这种属性.
另外抽象类AbstractCollection 是集合的父母!
他的子类都实现了iterator()方法获得这个迭代集合!所以
他的子类1!
AbstractList, AbstractQueue, AbstractSet, ArrayDeque
以及他的子类都会有此特性!!
例如 TreeSet,
中种特性的实现方法是借助于一个modCount成员变量,记录structual modification的次数,在Iterator初始化时,让它的成员变量expectedModCount等于modCount,这样在Iterator做遍历时,如果发现 expectedModCount!= modCount就说明容器的内容发生了改变,抛出ConcurrentModificationException异常