本文主要是介绍一个ConcurrentModificationException引发的血案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
这个错误时比较常见的,今天我们源码分析下里边的机制,这个是我们在遍历list或map时经常会看到这样的错误java.util.ConcurrentModificationException:at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
这个场景一般是我们是我循环遍历,根据某个条件来移除这个list或map中的对象
我们看下HashMap内部的实现 HashMap<K,V> 继承自 AbstractMap<K,V> ,AbstractMap<K,V>继承自Map<K,V>,看下Map里的实现
里边有一个静态的Entry<K,V> ,Entry可以理解为入口,接口里边定义了一个常规的方法;接口下面是一些Map常用的操作比如clear,remove,put等
AbstarctMap<K,V>继承自Map<K,V>
里边主要是封装了一些可能会共用到到方法,里边有Set<K>ketSet,可以理解为Key的集合,Collection<V>valuesCollection;value的集合,HashMap不就是Key-value嘛
HashMap继承自AbstractMap
public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable {使用这个ConcurrentModificationException关键字在HashMap中搜索一下发现上面的解释
* <p>The {@code Iterator} created by calling the {@code iterator} method * may throw a {@code ConcurrentModificationException} if the map is structurally * changed while an iterator is used to iterate over the elements. Only the * {@code remove} method that is provided by the iterator allows for removal of * elements during iteration. It is not possible to guarantee that this * mechanism works in all cases of unsynchronized concurrent modification. It * should only be used for debugging purposes. * * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values谷歌翻译
<p>通过调用{@code iterator}方法创建的{@code Iterator}
*如果映射是结构化的,可以抛出{@code ConcurrentModificationException}
*改变,而迭代器用于遍历元素。 只有
* {@code remove}方法由迭代器提供允许删除
*元素。 不可能保证这一点
*机制适用于所有非同步并发修改的情况。 它
*仅应用于调试目的。
意思很明了了,只有迭代器遍历时允许删除元素,其他方式如结构映射调用map.remove("key")等会抛出ConcurrentModificationException,这也是HashMap的Fail-Fast 快速失败机制,只允许迭代器这样处理,也许这也是迭代器的作用所在。
这个快速失败机制是怎么实现的呢,我们继续往下看
if (modCount != expectedModCount)throw new ConcurrentModificationException(); if (nextEntry == null)throw new NoSuchElementException();有两个变量,如果modCount != expectedModCount 则抛出这个异常,通过查找这个成员变量modCount 发现,不管map是remove,put,clear等modCount都会++,即modCount++,说明map操作数据了,数据被修改了,则次数增加;而expectedModCount 是指期待的操作次数,因为map.put 也会使modCount++,在HashMapIterator中有这样一段代码
private abstract class HashIterator {int nextIndex; HashMapEntry<K, V> nextEntry = entryForNullKey; HashMapEntry<K, V> lastEntryReturned; int expectedModCount = modCount;给expectedModCount 赋值 则其默认值就是map.size();为什么说是在Iterator中遍历remove数据不回导致上述的异常,我们来看下代码
public void remove() {if (lastEntryReturned == null)throw new IllegalStateException(); if (modCount != expectedModCount)throw new ConcurrentModificationException(); HashMap.this.remove(lastEntryReturned.key); lastEntryReturned = null; expectedModCount = modCount; }这个remove方法是在HashIterator中实现的,迭代器每次remove都会将其重新赋值,因此两者恒等,不回出现上述问题,迭代器可以理解为数据库的游标,每次都会指向下一个元素,如果元素存在,则remove,我建立了一个测试类,验证上述我说的问题
public class JavaReleations {public static void main(String...params){Map<Integer,String> map = new HashMap<>(); List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); map.put(1,"1"); map.put(2,"2"); map.put(3,"3"); //我们比较常用的这个map循环移除 for(Map.Entry<Integer,String> entry : map.entrySet()){System.out.println("map增强for循环:"+entry.getKey() + entry.getValue() + map.entrySet().size());map.remove(1);//外部结构移除会造成ConcurrentModificationException异常 } //迭代器循环移除 Iterator<Map.Entry<Integer,String >> iterator = map.entrySet().iterator(); while (iterator.hasNext()){
上述代码的注释已经说明问题,下面看下测试结果:System.out.println("迭代器循环:");iterator.next(); iterator.remove();//迭代器内部移除不会出现ConcurrentModificationException } //List的内部机制和HashMap类似,也有类似于HashMap的快速失败机制// for (String str : list){//// list.remove(str);//// }
map增强for循环:113
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at com.example.mrboudar.playboy.javarealations.JavaReleations.main(JavaReleations.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Process finished with exit code 1
看到log发现只执行了一次循环,第二次时modCount值于expectedModCount已经不相等了,因而抛出ConcurrentModificationException异常,循环停止,也导致java虚拟机异常中断,下面的迭代器循环都没有执行。
注释掉map循环后,测试结果如下:
迭代器循环:
迭代器循环:
迭代器循环:
Process finished with exit code 0
发现没有抛出ConcurrentModificationException异常,java虚拟机正常结束。
上面验证了只有Iterator迭代器循环时是相对安全的,不会抛出异常,当然了,如果你还是想用map循环,或者需求只能通过那种方式来做,可以使用break,找到符合条件的及时break掉循环,防止下次循环抛出异常,或者建立temp来存储等方式
相对于Map,List的实现与之类似,也是用modCount 上面两个变量用来标示快速失败,循环移除也最好使用迭代器方式,或者for循环中及时break掉
好了,这个错误基本上是这样,大家要看多源码,多理解其中的实现,与君共勉吧!
这篇关于一个ConcurrentModificationException引发的血案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!