当前位置: 首页 > news >正文

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();});}}}

解决问题

解决示例一

  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 等)。

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

相关文章:

Java/Kotlin HashMap 等集合引发 ConcurrentModificationException

在对一些非并发集合同时进行读写的时候&#xff0c;会抛出 ConcurrentModificationException 异常产生示例 示例一&#xff08;单线程&#xff09;&#xff1a; 遍历集合时候去修改 抛出 ConcurrentModificationException 的主要原因是当你在遍历一个集合&#xff08;如 Map…...

【Day31 LeetCode】动态规划DP Ⅳ

一、动态规划DP Ⅳ 1、最后一块石头的重量II 1049 这题有点像脑筋急转弯&#xff0c;尽量让石头分成重量相同的两堆&#xff08;尽可能相同&#xff09;&#xff0c;相撞之后剩下的石头就是最小的。明白这一点&#xff0c;就与上一篇博客里的划分等和数组很相似。划分等和数组…...

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

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

Ollama AI 开发助手完全指南:从入门到实践

本文将详细介绍如何使用 Ollama AI 开发助手来提升开发效率,包括环境搭建、模型选择、最佳实践等全方位内容。 © ivwdcwso (ID: u012172506) 目录 基础环境配置模型选择与使用开发工具集成实践应用场景性能优化与注意事项最佳实践总结一、基础环境配置 1.1 系统要求 在…...

Racecar Gym

Racecar Gym 参考&#xff1a;https://github.com/axelbr/racecar_gym/blob/master/README.md 1. 项目介绍 Racecar Gym 是一个基于 PyBullet 物理引擎的 reinforcement learning (RL) 训练环境&#xff0c;模拟微型 F1Tenth 竞速赛车。它兼容 Gym API 和 PettingZoo API&am…...

代码随想录36 动态规划

leetcode 343.整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 1…...

离散时间傅里叶变换(DTFT)公式详解:周期性与连续性剖析

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

深度学习|表示学习|卷积神经网络|Batch Normalization在干什么?|19

如是我闻&#xff1a; Batch Normalization&#xff08;批归一化&#xff0c;简称 BN&#xff09; 是 2015 年由 Ioffe 和 Szegedy 提出 的一种加速深度神经网络训练并提高稳定性的技术。 它的核心思想是&#xff1a;在每一层的输入进行归一化&#xff0c;使其均值接近 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&#xff0c;从而将计算密集型任务从主线程中分离出来&#xff0c;避免阻塞用户界面。以下是一些关键点&#xff1a; 优势 性能提升&#xff1a;将耗时的渲染…...

Android学习21 -- launcher

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

antd pro框架,使用antd组件修改组件样式

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

响应式编程_05 Project Reactor 框架

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

RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)

#作者&#xff1a;闫乾苓 文章目录 RabbitMQ简介RabbitMQ与VMware的关系架构工作流程RabbitMQ 队列工作模式及适用场景简单队列模式&#xff08;Simple Queue&#xff09;工作队列模式&#xff08;Work Queue&#xff09;发布/订阅模式&#xff08;Publish/Subscribe&#xff…...

导出依赖的几种方法

在 Python 中&#xff0c;你可以使用以下方法导出项目的依赖&#xff1a; 1. 使用 pip freeze pip freeze 可以列出当前环境中安装的所有包及其版本&#xff0c;并将结果保存到 requirements.txt 文件中。 pip freeze > requirements.txt2. 使用 pipreqs pipreqs 可以根…...

CS 与 BS 架构的差异

在数字化的今天&#xff0c;选择软件架构模式对系统的性能、维护、安全和成本都有很大影响。BS架构和CS架构是最常见的两种模式&#xff0c;了解它们的区别和特点对开发人员和企业决策者都很重要。 CS架构最早出现&#xff0c;当时用户直接从主机获取数据。随着客户端和服务端…...

OpenCV YOLOv11实时视频车辆计数线:让车辆进出有条理!

前言 大家好!今天我们聊个超级有趣的课题——如何用OpenCV结合YOLOv11进行实时视频车辆计数。是不是很炫酷?车辆进出全都清晰可见,连“跑车”都能精确统计!不过,别急,这可不仅仅是数车那么简单,背后还有许多实际问题等着你去搞定,比如计数线、车速、误检这些麻烦的小问…...

配置@别名路径,把@/ 解析为 src/

路径解析配置 webpack 安装 craco npm i -D craco/craco 项目根目录下创建文件 craco.config.js &#xff0c;内容如下 const path require(path) module.exports {webpack: {// 配置别名alias: {// 约定&#xff1a; 使用 表示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&#xff0f;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

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

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...