一个ConcurrentModificationException引发的血案

2023-12-01 12:08

本文主要是介绍一个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引发的血案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/441086

相关文章

一道算法题引发的动态内存管理的思考

在做PKU2762时,需要建邻接表。 于是按部就班写了下面一个插入边到邻接表中的函数: const int VMAX = 1010;typedef struct Graph{int vex;Graph* next;}Graph;Graph ArcGraph[VMAX];void insert(int u, int v){Graph* t = new Graph;Graph*

引发蛀牙、避免蛀牙食物大全

引发蛀牙、避免蛀牙食物大全 引发蛀牙的食物大全: 糖果 糖浆 糖果棒 巧克力 碳酸饮料 果汁 口香糖 蜂蜜 蛋糕 甜点 薯片 脆饼干 果酱 果冻 蜜饯 蜜饯果干 避免蛀牙的食物大全: 高纤维蔬菜 水果 坚果 种子 高钙乳制品 高蛋白质肉类 高蛋白质鱼类 绿茶 水 蔬菜汤 鸡汤 酸奶 酸奶制品 奶酪 红薯 土豆 面包和全麦面包 芝士

捉虫笔记(四)-- 空格引发的悬案

空格引发的悬案 1、描述现象: 在代码中有一段利用rmdir指令删除目录代码,但是有用户反馈一直删除失败,但是有没有看到错误的日志信息,正好有同事能复现,所以今天好好探究一番。 2、思考过程 很好奇的一点就是为什么有的环境就是正常。 首先想到2个问题: ①代码有没有执行。 ②假如执行,有没有错误。 关于这两个问题都有个难点,我该如何下断点: 2.1、分析代码是否执行 删除目录的

“苹果税”引发的苹果与腾讯、字节跳动之间的纷争与博弈

北京时间9月10日凌晨一点的Apple特别活动日渐临近,苹果这次将会带来iPhone16系列新品手机及其他硬件产品的更新,包括iPad、Apple Watch、AirPods等。从特别活动的宣传图和宣传标语“閃亮時刻”来看,Apple Intelligence将会是史上首次推出,无疑将会是iOS 18的重头戏和高光时刻。 不过就在9月2日,一则“微信可能不支持iPhone16”的

Navicat导入时由分号引发的诡异问题

最近在将第三方提供的一个sql导入到自己的数据库的时候,(Event部分的脚本)总是提示错误: [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)

概述 从 WWDC 23 开始,苹果推出了全新的数据库框架 SwiftData。它借助于 Swift 语言简洁而富有表现力的特点,抛弃了以往数据库所有的额外配置文件,只靠纯代码描述就可以干脆利索的让数据库的创建和增删改查(CRUD)一气呵成。 在本系列博文中,我们将从一个简单而“诡异”的运行“事故”开始,有理有据的深入探寻一番 SwiftData 中耐人寻味的“那些事儿”。 在本

Redis的incr命令引发的反序列化异常和ERR value is not an integer or out of range异常

在Java中使用inc命令的时候发现redis中的值被反序列化后居然不是数字,检查后发现可能是序列化器没对,在redis配置的地方将序列化器设置为 Jackson2JsonRedisSerializer后使用整成,贴上代码 @Bean(name = "RedisTemplate")@SuppressWarnings("all")public RedisTemplate<String,

java.util.ConcurrentModificationException 异常的解决办法

不论是在迭代还是普通的for循环中 , 如果出现一边遍历一边修改集合的情况 那么很有可能会出现 ConcurrentModificationException 异常 详细原因参考原文 java.util.ConcurrentModificationException 异常原因和解决方法  解决的方法是改成索引遍历 , 但是需要在删除之后保证索引的正常 其中集合 recruitList.

Failed resolution of: Lcom/growingio/android/sdk/agent/VdsAgent;删除growingio引发的问题

删除了 growingio之后 项目一直报这个错误 Failed resolution of: Lcom/growingio/android/sdk/agent/VdsAgent; 真是讨厌 解决方案 在as 的 Terminal 分别执行这两个命令 ./gradlew cleanBuildCache  ./gradlew clean 如果在使用上面两个命令的时候出现 权限拒绝

巴黎奥运会引发体育健身热潮:气膜体育馆成为新宠—轻空间

随着巴黎奥运会的成功举办,全球范围内掀起了一股体育健身的热潮。各地的健身场所迎来了前所未有的参与热情,其中,融合了体育、娱乐、休闲等多种业态的综合气膜体育馆因其独特的优势,迅速成为群众健身的新宠,成为了大众追求健康生活方式的热门去处。  多功能融合,满足多元化健身需求 综合气膜体育馆的最大特点是其多功能性。相比传统体育场馆,气膜结构建筑无须内部柱子支撑,提供了更加广阔和灵活的室内空间。