Java/Kotlin HashMap 等集合引发 ConcurrentModificationException

news2025/2/6 11:48:01

在对一些非并发集合同时进行读写的时候,会抛出 ConcurrentModificationException

异常产生示例

示例一(单线程): 遍历集合时候去修改

抛出 ConcurrentModificationException 的主要原因是当你在遍历一个集合(如 Map、List 或 Set)时,同时对该集合进行了结构性修改(例如添加或删除元素),而这些修改没有通过迭代器自身的相应方法进行。

    private static Map<String, String> map = new HashMap<>();

    static {
        for (int i = 0; i < 100000; i++) {
            map.put(String.valueOf(i), String.valueOf(i));
        }
    }

    public static void main(String[] args) {
        // 在循环的时候删除元素
        map.forEach((key, value) -> {
            if (Integer.parseInt(key) % 2 == 0){
                map.remove(key);
                return;
            }
            System.out.println(key + value);
        });
    }

示例二(多线程): 多线程读写

多线程读写和示例一抛出异常的原因一样

 private static Map<String, String> map = new HashMap<>();

    static {
        for (int i = 0; i < 100000; i++) {
            map.put(String.valueOf(i), String.valueOf(i));
        }
    }

    public static void main(String[] args) {
        // 并发修改 map 让其出现并发异常
        for (int i = 0; i < 100; i++) {
            new Thread(new Add(map)).start();
            new Thread(new Read(map)).start();
        }
    }

    static class Add implements Runnable {
        private HashMap<String, String> map;

        public Add(HashMap<String, String> map) {
            this.map = map;
        }

        @Override
        public void run() {
            map.clear();
            for (int i = 0; i < 100000; i++) {
                map.put(String.valueOf(i), String.valueOf(i));
            }
        }
    }

    static class Read implements Runnable {
        private HashMap<String, String> map;

        public Read(HashMap<String, String> map) {
            this.map = map;
        }

        @Override
        public void run() {
            CopyOnWriteArraySet<Map.Entry<String,String>> copyOnWriteArraySet = new CopyOnWriteArraySet<>(map.entrySet());
            for (int i = 0; i < 100000; i++) {
                copyOnWriteArraySet.forEach((entry) -> {
                    String a = entry.getKey() + entry.getValue();
                });
            }
        }
    }

解决问题

解决示例一

  1. CopyOnWriteArraySet

CopyOnWriteArraySet 是 Java 并发集合包中的一种线程安全的集合。它的关键特性在于“写时复制”(copy-on-write),这意味着在对集合进行修改操作(如添加或删除元素)时,其实现机制是创建底层数组的一个新副本,而不是直接在原数组上进行修改。这种机制使得在遍历 CopyOnWriteArraySet 时不会抛出 ConcurrentModificationException,因为迭代器是在数组的一个快照上工作的,它不受后续对集合进行的修改的影响。

CopyOnWriteArraySet<Map.Entry<String,String>> copyOnWriteArraySet = new CopyOnWriteArraySet<>(map.entrySet());
copyOnWriteArraySet.forEach((entry) -> {
    String key = entry.getKey();
    String value = entry.getValue();
    if (Integer.parseInt(key) % 2 == 0){
        map.remove(key);
        return;
    }
    System.out.println(key + value);
});
  1. 迭代器

当使用迭代器遍历集合时,直接调用集合的 remove 方法(如 map.remove(key))会导致 ConcurrentModificationException,因为这破坏了迭代器预期的遍历顺序。而迭代器自身提供的 remove 方法是唯一一种可以在遍历过程中安全移除元素的方法。该方法确保了在删除元素后,迭代器能够继续正确地跟踪集合的状态,不会导致并发修改异常。

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next();
    if (Integer.parseInt(entry.getKey()) % 2 == 0) {
        iterator.remove(); // 使用迭代器的remove方法来安全地移除元素
    } else {
        System.out.println(entry.getKey() + entry.getValue());
    }
}
  1. 替换原本的集合
map = map.entrySet().stream()
        .filter(entry -> Integer.parseInt(entry.getKey()) % 2 != 0)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, HashMap::new));
  1. 包装map
ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>(map);
concurrentHashMap.forEach((key, value) -> {
    if (Integer.parseInt(key) % 2 == 0){
        map.remove(key);
        return;
    }
    System.out.println(key + value);
});

方式二,也是替换 Map

ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>(map);
concurrentHashMap.forEach((key, value) -> {
    if (Integer.parseInt(key) % 2 == 0){
        concurrentHashMap .remove(key);
        return;
    }
    System.out.println(key + value);
});
map = concurrentHashMap 
  1. 使用 removeIf
 map.entrySet().removeIf(f-> Integer.parseInt(f.getKey()) % 2 == 0);

解决示例二

HashMap 本身并不是线程安全的,最直接有效的方法是直接还一个线程安全的集合
Java 提供了多种线程安全的集合,它们主要通过不同的方式来支持并发操作。以下是一些常见的线程安全集合:


1. 基于 java.util.concurrent 包的线程安全集合

这些集合在高并发场景下性能较好,适用于大多数现代应用。

1.1 ConcurrentHashMap
  • 特性:线程安全的哈希表,支持高效的并发读写。
  • 优势:读操作无锁,写操作基于分段锁(Java 8 后使用 CAS)。
  • 用法:
    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    
1.2 CopyOnWriteArrayList
  • 特性:在写操作时复制整个底层数组,因此适合读多写少的场景。
  • 优势:读操作无锁,写操作创建新数组,避免并发问题。
  • 用法:
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    
1.3 CopyOnWriteArraySet
  • 特性:基于 CopyOnWriteArrayList 实现,适合高频读取、低频修改的场景。
  • 用法:
    CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
    
1.4 ConcurrentLinkedQueue
  • 特性:基于链表的无界非阻塞线程安全队列。
  • 优势:采用 CAS 操作,适合高并发环境下的队列操作。
  • 用法:
    ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
    
1.5 ConcurrentLinkedDeque
  • 特性:双端队列版本的 ConcurrentLinkedQueue,支持高效的双向操作。
  • 用法:
    ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
    
1.6 LinkedBlockingQueue
  • 特性:基于链表的阻塞队列,支持可选的容量限制。
  • 优势:适合生产者-消费者模型。
  • 用法:
    LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
    
1.7 LinkedBlockingDeque
  • 特性:双端队列版本的 LinkedBlockingQueue,支持从两端进行操作。
  • 用法:
    LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<>(100);
    
1.8 ConcurrentSkipListMap
  • 特性:基于跳表的线程安全 SortedMap 实现,支持按键排序。
  • 用法:
    ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();
    
1.9 ConcurrentSkipListSet
  • 特性:基于 ConcurrentSkipListMap 实现的线程安全有序集合。
  • 用法:
    ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
    

2. 基于 Collections 工具类的同步集合

Collections.synchronizedXXX 方法可以将非线程安全的集合包装成线程安全集合,但性能不如 java.util.concurrent 包。

2.1 SynchronizedList
  • 特性:将普通 List 包装成线程安全集合。
  • 用法:
    List<String> list = Collections.synchronizedList(new ArrayList<>());
    
2.2 SynchronizedSet
  • 特性:将普通 Set 包装成线程安全集合。
  • 用法:
    Set<String> set = Collections.synchronizedSet(new HashSet<>());
    
2.3 SynchronizedMap
  • 特性:将普通 Map 包装成线程安全集合。
  • 用法:
    Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
    

注意:使用 Collections.synchronizedXXX 包装的集合在迭代时需要显式加锁:

synchronized (list) {
    for (String s : list) {
        // 操作
    }
}

3. Immutable Collections(不可变集合)

Java 9 引入了不可变集合,通过 List.of()Set.of()Map.of() 创建:

  • 特性:线程安全,不可修改,适合配置类或常量类数据。
  • 用法:
    List<String> list = List.of("a", "b", "c");
    Set<String> set = Set.of("a", "b", "c");
    Map<String, String> map = Map.of("key1", "value1", "key2", "value2");
    

4. Vector 和 Stack

这些是早期的线程安全集合:

  • Vector:线程安全的 List 实现。
    Vector<String> vector = new Vector<>();
    
  • Stack:线程安全的栈(继承自 Vector)。
    Stack<String> stack = new Stack<>();
    

但它们性能较低,通常不推荐使用。


推荐选择

  1. 高并发场景:优先使用 java.util.concurrent 包下的集合,例如 ConcurrentHashMapCopyOnWriteArrayListCopyOnWriteArraySet等。
  2. 读多写少:使用 CopyOnWriteArrayListCopyOnWriteArraySet
  3. 简易同步:使用 Collections.synchronizedXXX 包装集合。
  4. 不可修改数据:使用不可变集合(List.of 等)。

如果你有特定的应用场景,可以详细讨论选择最优集合!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

拍照对比,X70 PRO与X90 PRO+的细节差异

以下是局部截图&#xff08;上X70P下X90PP&#xff09; 对比1 这里看不出差异。 对比2 X90PP的字明显更清楚。 对比3 中下的字&#xff0c;X90PP显然更清楚。

Node.js与嵌入式开发:打破界限的创新结合

文章目录 一、Node.js的本质与核心优势1.1 什么是Node.js?1.2 嵌入式开发的范式转变二、Node.js与嵌入式结合的四大技术路径2.1 硬件交互层2.2 物联网协议栈2.3 边缘计算架构2.4 轻量化运行时方案三、实战案例:智能农业监测系统3.1 硬件配置3.2 软件架构3.3 核心代码片段四、…

使用java调用deepseek,调用大模型,处理问题。ollama

废话不多&#xff0c;直接上代码 Testpublic void test7171111231233(){// url:放请求地址String url "http://localhost:11434/api/generate";HttpRequest request HttpUtil.createPost(url);Map<String, String> headers new HashMap<>();String a…

Linux驱动---字符设备

目录 一、基础简介 1.1、Linux设备驱动分类 1.2、字符设备驱动概念 二、驱动基本构成 2.1、驱动模块的加载和卸载 2.2、添加LICENNSE以及其他信息 三、字符设备驱动开发步骤 3.1、分配主次设备号 3.1.1 主次设备号 3.1.2静态注册设备号 3.1.3动态注册设备号 3.1.4释…

php7.3安装php7.3-gmp扩展踩坑总结

环境&#xff1a; 容器里面为php7.3.3版本 服务器也为php7.3.3-14版本&#xff0c;但是因为业务量太大需要在服务器里面跑脚本 容器里面为 alpine 系统&#xff0c;安装各种扩展 服务器里面开发服为 ubuntu 16.04.7 LTS (Xenial Xerus) 系统 服务器线上为 ubuntu 20.04.6 LTS (…

javaEE-8.JVM(八股文系列)

目录 一.简介 二.JVM中的内存划分 JVM的内存划分图: 堆区:​编辑 栈区:​编辑 程序计数器&#xff1a;​编辑 元数据区&#xff1a;​编辑 经典笔试题&#xff1a; 三,JVM的类加载机制 1.加载: 2.验证: 3.准备: 4.解析: 5.初始化: 双亲委派模型 概念: JVM的类加…

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化&#xff1a;知识蒸馏的范式迁移与工程实践 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 摘要 在大型语言模型&#xff…

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析&#xff1f;一、大O表示法&#xff1a;复杂度的语言1.1 什么是大O&#xff1f;1.2 常见复杂度速查表 二、实战分析&#xff1a;解剖C语言代码2.1 循环结构的三重境界单层循环&#xff1a;线性时间双重循环&#xff1a;平方时间动态边界循环&…

[创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁

目录 前言&#xff1a; 从燃油汽车到智能汽车&#xff1a;工业革命下的价值变迁 前言&#xff1a; 燃油汽车&#xff0c;第一次、第二次工业革命&#xff0c;机械化、电气化时代的产物&#xff0c;以机械和电气自动化为核心价值。 智能汽车&#xff0c;第三次、第四次工业革…

vue页面和 iframe多页面无刷新方案和并行 并接入 micro 微前端部分思路

前: 新进了一家公司,公司是做电商平台的, 用的系统竟然还是jsp的网站,每次修改页面还需要我下载idea代码,作为一个前端, 这可不能忍,于是向上申请,意思你们后台做的太辣鸡,我要重做,经领导层商议从去年6月开始到今年12月把系统给重构了 公司系统采用的是每个jsp页面都是一个ifr…

Python 自学秘籍:开启编程之旅,人生苦短,我用python。

从2009年&#xff0c;用了几次python后就放弃了&#xff0c;一直用的php&#xff0c;现在人工智能时代&#xff0c;完全没php什么事情。必须搞python了&#xff0c;虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代&#xff…

每日一题洛谷P5721 【深基4.例6】数字直角三角形c++

#include<iostream> using namespace std; int main() {int n;cin >> n;int t 1;for (int i 0; i < n; i) {for (int j 0; j < n - i; j) {printf("%02d",t);t;}cout << endl;}return 0; }

解决DeepSeek服务器繁忙问题:本地部署与优化方案

deepseek服务器崩了&#xff0c;手把手教你如何在手机端部署一个VIP通道&#xff01; 引言 随着人工智能技术的快速发展&#xff0c;DeepSeek等大语言模型的应用越来越广泛。然而&#xff0c;许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…

【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)

【后端开发】系统设计101——通信协议&#xff0c;数据库与缓存&#xff0c;架构模式&#xff0c;微服务架构&#xff0c;支付系统&#xff08;36张图&#xff09; 文章目录 1、通信协议通信协议REST API 对比 GraphQL&#xff08;前端-web服务&#xff09;grpc如何工作&#x…

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…

武汉火影数字|VR虚拟现实:内容制作与互动科技的奇妙碰撞

VR虚拟现实是一种利用计算机技术生产三维虚拟世界的技术&#xff0c;通过头戴式显示器、手柄等设备&#xff0c;用户可以身临其境地感受虚拟世界&#xff0c;与其中的物体进行自然交互。 当内容制作遇上 VR&#xff0c;会发生什么&#xff1f; 当内容制作遇上VR&#xff0c;就像…

SpringBoot扩展篇:@Scope和@Lazy源码解析

SpringBoot扩展篇&#xff1a;Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …

tkvue 入门,像写html一样写tkinter

介绍 没有官网&#xff0c;只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…

Hackmyvm Connection

基本信息 难度&#xff1a;简单 靶机&#xff1a;192.168.194.11 kali&#xff1a;192.168.194.9 扫描 常规nmap扫描起手 nmap -sT -sV -A -T4 192.168.194.11 -p- 查看smb服务开启目录 139和445端口的smb服务直接以访客账号登录&#xff0c;无需密码验证成功。对应的ht…

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分

上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…