ConcurrentModificationException
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.
For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances. Some Iterator implementations (including those of all the general purpose collection implementations provided by the JRE) may choose to throw this exception if this behavior is detected. Iterators that do this are known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.
Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throwConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.
我自己的理解就是你不可以在遍历一个集合类的同时去改变它的大小,这样做是不对的,典型的就是例如这样:
for(String str:set){
set.remove(str);
}
解决方案通常是使用Iterator的remove方法,我对几个常用的集合类都试了,是可以的。这样做的原理在于:
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
所以只要记住“不在遍历一个集合类的同时去改变它的大小”,基本就可以理解了,这和是否多线程其实并没有关系,而且经过我的试验,改变集合中的内容是可以的,只要大小不变就行。以下是我的测试代码:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class TestCollection {
class A{
A(String v){
value = v;
}
String value = null;
@Override
public String toString() {
// TODO Auto-generated method stub
return value != null?value:"null";
}
}
/**
* @param args
*/
public static void main(String[] args) {
TestCollection t = new TestCollection();
t.testMap();
t.testSet();
t.testList();
}
private void testList() {
ArrayList<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
System.out.print("before~~~"+list);
//+++
//这样写会错,ConcurrentModificationException
// for(String str:set){
// set.remove(str);
// }
//---
Iterator i = list.iterator();
while(i.hasNext()){
System.out.print(""+i.next());
i.remove();
}
System.out.print("after~~~"+list);
}
private void testSet() {
Set<String> set = new HashSet<String>();
set.add("1");
set.add("2");
set.add("3");
set.add("4");
set.add("5");
set.add("6");
set.add("7");
System.out.print("before~~~"+set);
//+++
//这样写会错,ConcurrentModificationException
// for(String str:set){
// set.remove(str);
// }//---
Iterator i = set.iterator();
while(i.hasNext()){
System.out.print(""+i.next());
i.remove();
}
System.out.print("after~~~"+set);
}
private void testMap() {
// TODO Auto-generated method stub
Map<String, A> stringMap = new HashMap<String, A>();
stringMap.put("1", new A("Monday"));
stringMap.put("2", new A("Tuesday"));
stringMap.put("3", new A("Wensday"));
stringMap.put("4", new A("Wednesday"));
stringMap.put("5", new A("Friday"));
stringMap.put("6", new A("Saturday"));
stringMap.put("7", new A("Sunday"));
System.out.print("before~~~"+stringMap);
Iterator i = stringMap.keySet().iterator();
while(i.hasNext()){
String key = (String) i.next();
System.out.print(key);
//执行下面这句会报错
// stringMap.remove(key);
//但是下面这句不会报错
// stringMap.get(key).value = "Test";
i.remove();
}
System.out.print("after~~~"+stringMap);
}
}