【HashMap并发修改异常】

社区:幕后大佬


文章目录

    • HashMap并发修改异常
    • 使用HashTable
  • 使用工具类

  • 使用写时复制(CopyOnWrite)

  • 使用ConcurrentHashMap

  • fast-fail机制


本文的大概内容:

HashMap并发修改异常



HashMap实际使用过程中会出现一些线程安全问题,在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况,开多个线程不断进行put操作,rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插)所以最后的结果打乱了插入的顺序,就可能发生环形链和数据丢失的问题,引起死循环,导致CPU利用率接近100%。在jdk1.8中对HashMap进行了优化,发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程环境下,会发生数据覆盖的情况,如果没有hash碰撞的时候,它会直接插入元素。如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,线程A会把线程B插入的数据给覆盖,导致数据发生覆盖的情况,发生线程不安全。可参考【HashMap扩容机制】

实际的故障现象:java.util.ConcurrentModificationException并发修改异常。导致原因:并发争取修改导致,一个线程正在写,一个线程过来争抢,导致线程写的过程被其他线程打断,导致数据不一致。

使用HashTable

HashTable是线程安全的,只不过实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

使用工具类

线程同步:Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());

和Hashtable一样,实现上在操作HashMap时自动添加了synchronized来实现线程同步,都对整个map进行同步,在性能以及安全性方面不如ConcurrentHashMap。

使用写时复制(CopyOnWrite)

往一个容器里面加元素的时候,不直接往当前容器添加,而是先将当前容器的元素复制出来放到一个新的容器中,然后新的元素添加元素,添加完之后,再将原来容器的引用指向新的容器,这样就可以对它进行并发的读,不需要加锁,因为当前容器不添加任何元素。利用了读写分离的思想,读和写是不同的容器。

会有内存占用问题,在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存。

会有数据一致性问题,CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

使用ConcurrentHashMap

为了应对hashmap在并发环境下不安全问题可以使用,ConcurrentHashMap大量的利用了volatile,CAS等技术来减少锁竞争对于性能的影响。在JDK1.7版本中ConcurrentHashMap避免了对全局加锁,改成了局部加锁(分段锁),分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。不过这种结构的带来的副作用是Hash的过程要比普通的HashMap要长。

所以在JDK1.8版本中CurrentHashMap内部中的value使用volatile修饰,保证并发的可见性以及禁止指令重排,只不过volatile不保证原子性,使用为了确保原子性,采用CAS(比较交换)这种乐观锁来解决。

fast-fail机制


在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。其实就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报。

以下的代码是一个对两个整数做除法的方法,在fast_fail_method方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。

public int fast_fail_method(int arg1,int arg2){

if(arg2 == 0){

throw new RuntimeException(“can’t be zero”);

}

return arg1/arg2;

}

在Java集合类中很多地方都用到了该机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会触发该机制时,之后就会抛出并发修改异常ConcurrentModificationException。当然如果不在多线程环境下,如果在foreach遍历的时候使用add/remove方法,也可能会抛出该异常。

之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。


总结

以上就是今天要讲的内容,还希望各位读者大大能够在评论区积极参与讨论,给文章提出一些宝贵的意见或者建议,合理的内容,我会采纳更新博文,重新分享给大家。


提示:以下都是资源分享,求个一键三连。

博客封面

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

### ConcurrentModificationException 异常的原因及解决方案 #### 异常原因 `java.util.ConcurrentModificationException` 是 Java 中的一种运行时异常,通常发生在使用迭代器(Iterator)遍历集合对象(如 `ArrayList`、`HashMap` 等)的过程中。当集合的结构被非迭代器自身的方法修改时(例如通过 `add` 或 `remove` 方法),就会抛出此异常[^2]。 这种异常的核心机制在于迭代器的 **fail-fast** 特性。在遍历过程中,迭代器会检查集合的修改计数器(`modCount`)是否与预期值(`expectedModCount`)一致。如果不一致,则说明集合在遍历过程中发生了结构上的修改,从而抛出 `ConcurrentModificationException`[^4]。 以下是异常检测源码的关键部分: ```java final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } ``` 上述代码展示了迭代器在每次调用 `next()` 方法时,都会执行 `checkForComodification()` 方法以确保集合未被并发修改。如果发现修改计数器不匹配,则立即抛出异常[^1]。 #### 解决方案 为避免 `ConcurrentModificationException`,可以采用以下几种方法: 1. **使用 Iterator 的 remove 方法** 在遍历集合时,应通过迭代器自身的 `remove()` 方法来移除元素,而不是直接调用集合的 `remove()` 方法。例如: ```java List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer value = iterator.next(); if (value == 5) { iterator.remove(); // 使用迭代器的 remove 方法 } } ``` 2. **使用线程安全的集合类** 如果需要在多线程环境中操作集合,可以考虑使用线程安全的集合实现,例如 `CopyOnWriteArrayList` 或 `ConcurrentHashMap`。这些集合类通过内部机制保证了线程安全性,避免了 `ConcurrentModificationException` 的发生[^3]。 3. **使用同步块** 对于非线程安全的集合类,可以通过加锁的方式保护集合的访问和修改。例如: ```java List<Integer> list = Collections.synchronizedList(new ArrayList<>()); synchronized (list) { Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer value = iterator.next(); if (value == 5) { iterator.remove(); } } } ``` 4. **避免在增强 for 循环中修改集合** 增强 for 循环本质上也是基于迭代器实现的,因此在循环体内直接调用集合的 `remove()` 方法会导致异常。例如以下代码会抛出异常: ```java List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(i); } for (Integer value : list) { // 增强 for 循环 if (value == 5) { list.remove(value); // 抛出 ConcurrentModificationException } } ``` 正确的做法是改用显式的迭代器进行操作。 #### 示例代码 以下是一个完整的示例,展示如何正确处理 `ConcurrentModificationException`: ```java import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(i); } // 正确方式:使用 Iterator 的 remove 方法 Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer value = iterator.next(); if (value == 5) { iterator.remove(); // 安全移除 } } System.out.println(list); // 输出结果:[0, 1, 2, 3, 4, 6, 7, 8, 9] } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值