当前位置: 首页 > 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;家庭版、专业版、企业版、教育…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...