Java/Kotlin HashMap 等集合引发 ConcurrentModificationException
在对一些非并发集合同时进行读写的时候,会抛出 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;}@Overridepublic 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;}@Overridepublic 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();});}}}
解决问题
解决示例一
- 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);
});
- 迭代器
当使用迭代器遍历集合时,直接调用集合的 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());}
}
- 替换原本的集合
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));
- 包装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
- 使用 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<>();
但它们性能较低,通常不推荐使用。
推荐选择
- 高并发场景:优先使用
java.util.concurrent包下的集合,例如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet等。 - 读多写少:使用
CopyOnWriteArrayList或CopyOnWriteArraySet。 - 简易同步:使用
Collections.synchronizedXXX包装集合。 - 不可修改数据:使用不可变集合(
List.of等)。
如果你有特定的应用场景,可以详细讨论选择最优集合!
相关文章:
Java/Kotlin HashMap 等集合引发 ConcurrentModificationException
在对一些非并发集合同时进行读写的时候,会抛出 ConcurrentModificationException 异常产生示例 示例一(单线程): 遍历集合时候去修改 抛出 ConcurrentModificationException 的主要原因是当你在遍历一个集合(如 Map…...
拍照对比,X70 PRO与X90 PRO+的细节差异
以下是局部截图(上X70P下X90PP) 对比1 这里看不出差异。 对比2 X90PP的字明显更清楚。 对比3 中下的字,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
废话不多,直接上代码 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扩展踩坑总结
环境: 容器里面为php7.3.3版本 服务器也为php7.3.3-14版本,但是因为业务量太大需要在服务器里面跑脚本 容器里面为 alpine 系统,安装各种扩展 服务器里面开发服为 ubuntu 16.04.7 LTS (Xenial Xerus) 系统 服务器线上为 ubuntu 20.04.6 LTS (…...
javaEE-8.JVM(八股文系列)
目录 一.简介 二.JVM中的内存划分 JVM的内存划分图: 堆区:编辑 栈区:编辑 程序计数器:编辑 元数据区:编辑 经典笔试题: 三,JVM的类加载机制 1.加载: 2.验证: 3.准备: 4.解析: 5.初始化: 双亲委派模型 概念: JVM的类加…...
大语言模型轻量化:知识蒸馏的范式迁移与工程实践
大语言模型轻量化:知识蒸馏的范式迁移与工程实践 🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 摘要 在大型语言模型ÿ…...
数据结构:时间复杂度
文章目录 为什么需要时间复杂度分析?一、大O表示法:复杂度的语言1.1 什么是大O?1.2 常见复杂度速查表 二、实战分析:解剖C语言代码2.1 循环结构的三重境界单层循环:线性时间双重循环:平方时间动态边界循环&…...
[创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁
目录 前言: 从燃油汽车到智能汽车:工业革命下的价值变迁 前言: 燃油汽车,第一次、第二次工业革命,机械化、电气化时代的产物,以机械和电气自动化为核心价值。 智能汽车,第三次、第四次工业革…...
vue页面和 iframe多页面无刷新方案和并行 并接入 micro 微前端部分思路
前: 新进了一家公司,公司是做电商平台的, 用的系统竟然还是jsp的网站,每次修改页面还需要我下载idea代码,作为一个前端, 这可不能忍,于是向上申请,意思你们后台做的太辣鸡,我要重做,经领导层商议从去年6月开始到今年12月把系统给重构了 公司系统采用的是每个jsp页面都是一个ifr…...
Linux特权组全解析:识别GID带来的权限提升风险
组ID(Group ID,简称 GID)是Linux系统中用来标识不同用户组的唯一数字标识符。每个用户组都有一个对应的 GID,通过 GID,系统能够区分并管理不同的用户组。 在Linux系统中,系统用户和组的配置文件通常包括以…...
RTMP 和 WebRTC
WebRTC(Web Real-Time Communication)和 RTMP(Real-Time Messaging Protocol)是两种完全不同的流媒体协议,设计目标、协议栈、交互流程和应用场景均有显著差异。以下是两者的详细对比,涵盖协议字段、交互流程及核心设计思想。 一、协议栈与设计目标对比 特性RTMPWebRTC传…...
系统通解:超多视角理解
在科学研究和工程应用中,我们常常面临各种复杂系统,需要精确描述其行为和变化规律。从物理世界的运动现象,到化学反应的进程,再到材料在受力时的响应,这些系统的行为往往由一系列数学方程来刻画。通解,正是…...
11.享元模式 (Flyweight)
定义 Flyweight 模式(享元模式) 是一种结构型设计模式,它旨在通过共享对象来有效支持大量细粒度对象的复用。该模式主要通过共享细节来减少内存使用,提升性能,尤其在需要大量对象时非常有效。 基本思想: …...
Python 自学秘籍:开启编程之旅,人生苦短,我用python。
从2009年,用了几次python后就放弃了,一直用的php,现在人工智能时代,完全没php什么事情。必须搞python了,虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代ÿ…...
验证工具:SVN版本控制
1-SVN概念 SVN(Subversion)是一种集中式版本控制系统,它用于文件和目录的版本管理,允许多个用户协同工作,同时追踪每个文件和目录的历史修改记录。以下是关于SVN版本控制的详细介绍: 一、SVN的基本概念 仓库(Repository):SVN的仓库是一个集中存储所有文件和目录的地…...
每日一题洛谷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; }...
React开发中箭头函数返回值陷阱的深度解析
React开发中箭头函数返回值陷阱的深度解析 一、箭头函数的隐式返回机制:简洁背后的规则二、块函数体中的显式返回要求:容易被忽视的细节三、真实场景下的案例分析案例1:忘记return导致组件渲染失败案例2:异步操作中的返回值陷阱 四…...
解决每次打开终端都需要source ~/.bashrc的问题(记录)
新服务器或者电脑通常需要设置一些环境变量,例如新电脑安装了Anaconda等软件,在配置环境变量后发现每次都需要重新source,非常麻烦,执行下面添加脚本实现一劳永逸 vim .bash_profile# .bash_profileif [ -f ~/.bashrc ]; then. ~…...
解决DeepSeek服务器繁忙问题:本地部署与优化方案
deepseek服务器崩了,手把手教你如何在手机端部署一个VIP通道! 引言 随着人工智能技术的快速发展,DeepSeek等大语言模型的应用越来越广泛。然而,许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…...
【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)
【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图) 文章目录 1、通信协议通信协议REST API 对比 GraphQL(前端-web服务)grpc如何工作&#x…...
Java基础——分层解耦——IOC和DI入门
目录 三层架构 Controller Service Dao 编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理? 容器怎样才能提供依赖的bean对象呢? 三层架构 Controller 控制…...
武汉火影数字|VR虚拟现实:内容制作与互动科技的奇妙碰撞
VR虚拟现实是一种利用计算机技术生产三维虚拟世界的技术,通过头戴式显示器、手柄等设备,用户可以身临其境地感受虚拟世界,与其中的物体进行自然交互。 当内容制作遇上 VR,会发生什么? 当内容制作遇上VR,就像…...
一文了解性能优化的方法
背景 在应用上线后,用户感知较明显的,除了功能满足需求之外,再者就是程序的性能了。因此,在日常开发中,我们除了满足基本的功能之外,还应该考虑性能因素。关注并可以优化程序性能,也是体现开发能…...
SpringBoot扩展篇:@Scope和@Lazy源码解析
SpringBoot扩展篇:Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …...
tkvue 入门,像写html一样写tkinter
介绍 没有官网,只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…...
c++ stl 遍历算法和查找算法
概述: 算法主要由头文件<algorithm> <functional> <numeric> 提供 <algorithm> 是所有 STL 头文件中最大的一个,提供了超过 90 个支持各种各样算法的函数,包括排序、合并、搜索、去重、分解、遍历、数值交换、拷贝和…...
Hackmyvm Connection
基本信息 难度:简单 靶机:192.168.194.11 kali:192.168.194.9 扫描 常规nmap扫描起手 nmap -sT -sV -A -T4 192.168.194.11 -p- 查看smb服务开启目录 139和445端口的smb服务直接以访客账号登录,无需密码验证成功。对应的ht…...
内置渲染管线和通用渲染管线的区别
内置渲染管线和通用渲染管线(URP)有以下区别: 功能特性 内置渲染管线:提供了一套较为基础的渲染功能,包括几何渲染、光照计算、阴影生成和后期处理等基本环节。但自定义选项相对有限,渲染次序基本是固…...
