直接看下面的代码
@Test
public void test4(){
try {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
Set<Integer> set = map.keySet();
for(Integer k : set){
map.remove(k);
}
} catch (Exception e) {
e.printStackTrace();
}
}
允许测试代码出现错误java.util.ConcurrentModificationException:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at cn.lovecto.test.SimpleTest.test4(SimpleTest.java:77)
我们来看报错的地方,HashMap.java中第828行:
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
再看报错行HashMap.java中的793,报错原因是modCount不等于expectedModCount:
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
我们再来看modCount的定义:
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
modCount是HashMap的一个成员变量,该值表示对HashMap的修改次数,查看HashMap的put()和remove()方法就可以发现,每次调用put()方法或者remove()方法就会对modCount进行加1操作,下面是put操作:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
expectedModCount是HashMap内部类HashIterator的成员变量,expectedModCount的值在构造的时候初始化为modCount,以后只有在remove的时候改变,构造代码片段:
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
remove代码片段:
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;
}
OK,找到了modCount和expectedModCount之间的关系,那么解决办法就有了。当使用HashMap的keySet方法获取key集合后,使用迭代器Iterator的方式remove:
@Test
public void test7(){
try {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
Set<Integer> set = map.keySet();
Iterator<Integer> it = set.iterator();
while (it.hasNext()) {
Integer v = it.next();
it.remove();
System.out.println(map);
}
} catch (Exception e) {
e.printStackTrace();
}
}
同理,除了Map外,类似List,Set等集合类,遍历的时候如果有修改操作,尽量使用Iterator:
//错误的
@Test
public void test5(){
try {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
for(Integer k : list){
list.remove(k);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//正确的
@Test
public void test6(){
try {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
} catch (Exception e) {
e.printStackTrace();
}
}
java.util.ConcurrentModificationException问题通常是由于遍历的时候对集合或map发生了修改导致,使用Iterator对集合或map进行比那里操作是更好的选择。