我发现大部分博文对快速失败机制是这样描述的:
在使用迭代器对集合对象进行遍历的时候,如果 A 线程正在对集合进行遍历,此时 B 线程对集合进行修改(增加、删除、修改),或者 A 线程在遍历过程中对集合进行修改,都会导致 A 线程抛出 ConcurrentModificationException 异常。
给出的代码示例如下:
HashMap hashMap = new HashMap();
hashMap.put("不只Java-1", 1);
hashMap.put("不只Java-2", 2);
hashMap.put("不只Java-3", 3);
Set set = hashMap.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
hashMap.put("下次循环会抛异常", 4);
System.out.println("此时 hashMap 长度为" + hashMap.size());
}
乍看起来似乎没有问题。然而在阅读HashMap源码时我发现这个说法其实不甚准确。更准确的说法是:
在使用迭代器对集合对象进行遍历的时候,如果有操作对HashMap的结构产生了影响,才会导致抛出 ConcurrentModificationException 异常。
HashMap关键代码如下:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//数组
Node<K,V>[] tab;
//当前元素
Node<K,V> p;
// n 数组长度
int n, i;
//复制tab元素,并将当前tab长度赋值给n
if ((tab = table) == null || (n = tab.length) == 0){
n = (tab = resize()).length;
}
if ((p = tab[i = (n - 1) & hash]) == null){
//获取第一个节点 如果节点为空 直接创建新节点
tab[i] = newNode(hash, key, value, null);
} else {
//中间代码省略
if (e != null) { // existing mapping for key 存在相同key情况处理
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null){
//更改当前key对应的值
e.value = value;
}
//此方法供LinkedListMap使用
afterNodeAccess(e);
return oldValue;
}
}
//完成新增操作后更新被修改次数
++modCount;
//大于负载后进行扩容
if (++size > threshold){
resize();
}
afterNodeInsertion(evict);
return null;
}
我们可以发现在put
方法中modCount
的增加有一个前提,就是只有在key不存在的情况下才会进行自增操作,在进行覆盖时并不会出现自增。也就是说,如果是覆盖HashMap中原有的key的话并不会触发ConcurrentModificationException。例如下面所示代码,在遍历中对Map进行了修改 但是并无异常抛出。
Map map =new HashMap();
map.put("不只Java-1","111");
map.put("不只Java-2","222");
Iterator iterable = map.entrySet().iterator();
while(iterable.hasNext()){
map.put("不只Java-1","333");
System.out.println(iterable.next());
}
区别在哪里呢?
仔细分析不难发现唯一的区别在于key的覆盖并没有更改Map的结构。无论此key的存储方式是链表还是树,key的覆盖都只是简单的替换。遍历下去都可以正常的获取到所有值。但是涉及到Map结构修改的操作都有可能导致遍历无法遍历到所有值,因此才会触发ConcurrentModificationException。