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…...
【Day31 LeetCode】动态规划DP Ⅳ
一、动态规划DP Ⅳ 1、最后一块石头的重量II 1049 这题有点像脑筋急转弯,尽量让石头分成重量相同的两堆(尽可能相同),相撞之后剩下的石头就是最小的。明白这一点,就与上一篇博客里的划分等和数组很相似。划分等和数组…...

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分
上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…...
Ollama AI 开发助手完全指南:从入门到实践
本文将详细介绍如何使用 Ollama AI 开发助手来提升开发效率,包括环境搭建、模型选择、最佳实践等全方位内容。 © ivwdcwso (ID: u012172506) 目录 基础环境配置模型选择与使用开发工具集成实践应用场景性能优化与注意事项最佳实践总结一、基础环境配置 1.1 系统要求 在…...

Racecar Gym
Racecar Gym 参考:https://github.com/axelbr/racecar_gym/blob/master/README.md 1. 项目介绍 Racecar Gym 是一个基于 PyBullet 物理引擎的 reinforcement learning (RL) 训练环境,模拟微型 F1Tenth 竞速赛车。它兼容 Gym API 和 PettingZoo API&am…...
代码随想录36 动态规划
leetcode 343.整数拆分 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k > 2 ),并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 1…...

离散时间傅里叶变换(DTFT)公式详解:周期性与连续性剖析
摘要 离散时间傅里叶变换(DTFT)是数字信号处理领域的重要工具,它能将离散时间信号从时域转换到频域,揭示信号的频率特性。本文将深入解读DTFT公式,详细阐述其具有周期性和连续性的原因,帮助读者全面理解DT…...

深度学习|表示学习|卷积神经网络|Batch Normalization在干什么?|19
如是我闻: Batch Normalization(批归一化,简称 BN) 是 2015 年由 Ioffe 和 Szegedy 提出 的一种加速深度神经网络训练并提高稳定性的技术。 它的核心思想是:在每一层的输入进行归一化,使其均值接近 0&…...

Go基础之环境搭建
文章目录 1 Go 1.1 简介 1.1.1 定义1.1.2 特点用途 1.2 环境配置 1.2.1 下载安装1.2.2 环境配置 1.2.2.1 添加环境变量1.2.2.2 各个环境变量理解 1.2.3 验证环境变量 1.3 包管理工具 Go Modules 1.3.1 开启使用1.3.2 添加依赖包1.3.3 配置国内包源 1.3.3.1 通过 go env 配置1.…...
echarts、canvas这种渲染耗时的工作能不能放在webworker中做?
可以将 ECharts、Canvas 等渲染耗时的工作放在 Web Worker 中进行处理。Web Worker 允许在后台线程中运行 JavaScript,从而将计算密集型任务从主线程中分离出来,避免阻塞用户界面。以下是一些关键点: 优势 性能提升:将耗时的渲染…...

Android学习21 -- launcher
1 前言 之前在工作中,第一次听到launcher有点蒙圈,不知道是啥,当时还赶鸭子上架去和客户PK launcher的事。后来才知道其实就是安卓的桌面。本来还以为很复杂,毕竟之前接触过windows的桌面,那叫一个复杂。。。 后面查了…...

antd pro框架,使用antd组件修改组件样式
首先用控制台的指针找到组件的类名 然后找到项目的src/global.less文件 在里面进行修改,切记:where(.css-dev-only-do-not-override-5fybr3).ant-input:placeholder-shown这种格式,把where(.css-dev-only-do-not-override-5fybr3)删掉,使用…...

响应式编程_05 Project Reactor 框架
文章目录 概述响应式流的主流实现框架RxJavaReactor Project Reactor 框架Reactor 异步数据序列Flux 和 Mono 组件FluxMono 操作符背压处理 小结 概述 响应式编程_02基本概念:背压机制 Backpressure介绍了响应式流规范以及 Spring 框架中的响应式编程技术ÿ…...

RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)
#作者:闫乾苓 文章目录 RabbitMQ简介RabbitMQ与VMware的关系架构工作流程RabbitMQ 队列工作模式及适用场景简单队列模式(Simple Queue)工作队列模式(Work Queue)发布/订阅模式(Publish/Subscribeÿ…...
导出依赖的几种方法
在 Python 中,你可以使用以下方法导出项目的依赖: 1. 使用 pip freeze pip freeze 可以列出当前环境中安装的所有包及其版本,并将结果保存到 requirements.txt 文件中。 pip freeze > requirements.txt2. 使用 pipreqs pipreqs 可以根…...

CS 与 BS 架构的差异
在数字化的今天,选择软件架构模式对系统的性能、维护、安全和成本都有很大影响。BS架构和CS架构是最常见的两种模式,了解它们的区别和特点对开发人员和企业决策者都很重要。 CS架构最早出现,当时用户直接从主机获取数据。随着客户端和服务端…...
OpenCV YOLOv11实时视频车辆计数线:让车辆进出有条理!
前言 大家好!今天我们聊个超级有趣的课题——如何用OpenCV结合YOLOv11进行实时视频车辆计数。是不是很炫酷?车辆进出全都清晰可见,连“跑车”都能精确统计!不过,别急,这可不仅仅是数车那么简单,背后还有许多实际问题等着你去搞定,比如计数线、车速、误检这些麻烦的小问…...

配置@别名路径,把@/ 解析为 src/
路径解析配置 webpack 安装 craco npm i -D craco/craco 项目根目录下创建文件 craco.config.js ,内容如下 const path require(path) module.exports {webpack: {// 配置别名alias: {// 约定: 使用 表示src文件所在路径: path.resolve(__dirname,src)…...
java 进阶教程_Java进阶教程 第2版
第2版前言 第1版前言 语言基础篇 第1章 Java语言概述 1.1 Java语言简介 1.1.1 Java语言的发展历程 1.1.2 Java的版本历史 1.1.3 Java语言与C/C 1.1.4 Java的特点 1.2 JDK和Java开发环境及工作原理 1.2.1 JDK 1.2.2 Java开发环境 1.2.3 Java工作原理 1.…...

Windows Docker笔记-安装docker
安装环境 操作系统:Windows 11 家庭中文版 docker版本:Docker Desktop version: 4.36.0 (175267) 注意: Docker Desktop 支持以下Windows操作系统: 支持的版本:Windows 10(家庭版、专业版、企业版、教育…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...