[疑难杂症2024-001] java多线程运行时遇到java.util.ConcurrentModificationException的解决方案

news2024/11/13 15:22:35

本文由Markdown语法编辑器编辑完成。

1.背景

由于近日在改进一个医学图像的收图服务。之前的版本,我们采用了pynetdicom的服务。
https://pydicom.github.io/pynetdicom/stable/

它的介绍为:
pynetdicom is a pure Python package that implements the DICOM networking protocol. Working with pydicom, it allows the easy creation of DICOM Application Entities (AEs), which can then act as Service Class Users (SCUs) and Service Class Providers (SCPs) by associating with other AEs and using or providing the services available to the association.

是用python实现了DICOM的网络传输协议。这对于快速搭建服务,是很方便的一个选择。但是在实际的应用中,我们发现,如果医院同时有多个终端,比如多台CT机,多个PACS客户端,同时向它发送请求时,它的性能会遇到一定的瓶颈。

因此,我们最终还是采用了基于java的dcm4chee来实现。

具体在实现时,我们会遇到一个处理场景是,需要开启两个线程。
一个线程负责持续不断的接收CT机器发过来的图像;另一个线程,则需要定时不间断地(比如,每隔3s), 进行一次轮询,将符合一定条件的图像,拿走进行后续的处理。

这个场景,非常类似于银行账户。比如同一时刻或非常接近的时刻,有一个账户,有人往这个账户存钱,而有人则从这个账户取钱。那么这两个行为,就相当于是两个运行中的线程。而账户的钱,实际类似于数据库中的记录。一个线程,是要增加账户的钱,另一个账户,则是要减少这个账户的钱。
在这里插入图片描述

因此,如何来保证,这个账户的钱,计算是准确的呢。也即,如何确保两个线程,在修改同一个对象的值时,不至于发生错乱,导致不一致的情况呢?

我们会采用锁的机制。哪个线程先来访问,那么它就先拿到锁,然后进行一系列的操作,操作完成后,再释放锁。当另一个线程也需要访问这个对象时,如果发现被锁了,则会等待,直到这个锁被释放后,它才可以进行操作。

2. 问题根源

虽然我也知道多线程访问时需要上锁。但是在实际编写代码时,还是忽略了一个地方。导致,我加的定时轮询任务,在没有过多数据访问时,还是可以正常工作的。
但是一旦大量的数据过来时,定时任务就罢工了。因此,数据就变成了只进不出,造成了数据处理的堆积。

后来通过在代码的入口处,增加了try…catch的异常捕获逻辑。
再次运行时,可以看到在某一次定时任务运行的时候,发生了异常。异常的名称为ConcurrentModificationException。

顾名思义,这个异常就是说明了,有不同的线程,在同时修改一个变量(在我的项目,这里是一个HashMap)。也就是说,一个线程在往这个HashMap里面,新增元素;而另一个线程,则在轮询判断这个HashMap中是否有符合条件,应该被pop掉的元素。

解决方案:

2.1 使用迭代器

使用Iterator进行遍历和修改:在遍历集合时,使用Iterator的remove()方法来删除元素,而不是直接在集合上进行修改。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class AvoidConcurrentModification {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

		# 定义一个迭代器,通过遍历迭代器的方式,可以实现边迭代,边修改的操作。
        Iterator<Integer> iterator = numbers.iterator();
        while (iterator.hasNext()) {
            Integer number = iterator.next();
            if (number == 2) {
                iterator.remove(); // 使用迭代器的remove()方法
            }
        }
    }
}

2.2 使用并发集合(Concurrent Collections)

Java提供了一些线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等。这些集合类使用了特定的并发控制机制,可以在多线程环境下安全地进行遍历和修改操作。根据具体的需求,选择合适的并发集合类来代替普通的集合类。

2.3 使用同步(Synchronization)

如果你必须使用普通的集合类,并且需要在多线程环境下进行遍历和修改操作,你可以使用同步机制来确保线程安全。使用synchronized关键字或者使用Collections.synchronizedXXX()方法包装集合类,以确保每次只有一个线程可以访问集合的修改操作。比如:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class AvoidConcurrentModification {
    public static void main(String[] args) {
        List<Integer> numbers = Collections.synchronizedList(new ArrayList<>());
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        synchronized (numbers) {
            Iterator<Integer> iterator = numbers.iterator();
            while (iterator.hasNext()) {
                Integer number = iterator.next();
                if (number == 2) {
                    iterator.remove();
                }
            }
        }
    }
}

2.4 使用线程锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


private Lock lock = new ReentrantLock();


import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class AvoidConcurrentModification {

	List<Integer> numbers = Collections.synchronizedList(new ArrayList<>());

    public static void addItem(String[] args) {
   		 # 首先拿到线程锁
		lock.lock();

        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

		# 处理完毕后,释放线程锁.
		lock.unlock();
    }

	public static void removeItem(String[] args) {
   		 # 首先拿到线程锁
		lock.lock();

		for (String item : numbers ) {
			if (item == 2) {
				numbers.remove(item);
			}

		# 处理完毕后,释放线程锁.
		lock.unlock();
    }
}

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

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

相关文章

(2024,仅高频分量的蓝噪声与高斯噪声线性插值,时变噪声)扩散模型的蓝噪声

Blue noise for diffusion models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 简介 2. 相关工作 3. 方法 3.1 相关噪声 3.2 具有时变噪声的扩散模型 3.3 利用矫正…

Unity3d Shader篇(六)— BlinnPhong高光反射着色器

文章目录 前言一、BlinnPhong高光反射着色器是什么&#xff1f;1. BlinnPhong高光反射着色器的工作原理2. BlinnPhong高光反射着色器的优缺点优点缺点 3. 公式 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三…

Nginx限流设置

1.反向代理(建议先看正向代理,反向代理则是同样你要与对方服务器建立连接,但是,代理服务器和目标服务器在一个LAN下,所以我们需要与代理服务器先建交,再由他获取与目标服务器的交互,好比一个带刀侍卫守护着目标服务器) 屏蔽目标服务器的真实地址&#xff0c;相对安全性较好&am…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之StepperItem组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之StepperItem组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、StepperItem组件 用作Stepper组件的页面子组件。 子组件 无。 接口 St…

Kafka 生产调优

Kafka生产调优 文章目录 Kafka生产调优一、Kafka 硬件配置选择场景说明服务器台数选择磁盘选择内存选择CPU选择 二、Kafka Broker调优Broker 核心参数配置服役新节点/退役旧节点增加副本因子调整分区副本存储 三、Kafka 生产者调优生产者如何提高吞吐量数据可靠性数据去重数据乱…

如何写一个其他人可以使用的GitHub Action

前言 在GitHub中&#xff0c;你肯定会使用GitHub Actions自动部署一个项目到GitHub Page上&#xff0c;在这个过程中总要使用workflows工作流&#xff0c;并在其中使用action&#xff0c;在这个使用的过程中&#xff0c;总会好奇怎么去写一个action呢&#xff0c;所以&#xff…

无人机图像识别技术研究及应用,无人机AI算法技术理论,无人机飞行控制识别算法详解

在现代科技领域中&#xff0c;无人机技术是一个备受瞩目的领域。随着人们对无人机应用的需求在不断增加&#xff0c;无人机技术也在不断发展和改进。在众多的无人机技术中&#xff0c;无人机图像识别技术是其中之一。 无人机图像识别技术是利用计算机视觉技术对无人机拍摄的图像…

Project 2010下载安装教程,保姆级教程,附安装包和工具

前言 Project是一款项目管理软件&#xff0c;不仅可以快速、准确地创建项目计划&#xff0c;而且可以帮助项目经理实现项目进度、成本的控制、分析和预测&#xff0c;使项目工期大大缩短&#xff0c;资源得到有效利用&#xff0c;提高经济效益。软件设计目的在于协助专案经理发…

6、5 门关于 AI 和 ChatGPT 的免费课程,带您从 0-100

5 门关于 AI 和 ChatGPT 的免费课程,带您从 0-100 想在 2024 年免费了解有关 AI 和 ChatGPT 的更多信息吗? 图片由 DALLE 3 提供 活着是多么美好的时光啊。还有什么比现在更适合了解生成式人工智能(尤其是 ChatGPT)等人工智能元素的呢!许多人对这个行业感兴趣,但有些…

一文读懂:MybatisPlus从入门到进阶

快速入门 简介 在项目开发中&#xff0c;Mybatis已经为我们简化了代码编写。 但是我们仍需要编写很多单表CURD语句&#xff0c;MybatisPlus可以进一步简化Mybatis。 MybatisPlus官方文档&#xff1a;https://www.baomidou.com/&#xff0c;感谢苞米豆和黑马程序员。 Mybat…

表单标记(html)

前言 发现input的type属性还是有挺多的&#xff0c;这里把一些常用的总结一下。 HTML 输入类型 (w3school.com.cn)https://www.w3school.com.cn/html/html_form_input_types.asp text-文本 文本输入,如果文字太长&#xff0c;超出的部分就不会显示。 定义供文本输入的单行…

C语言操作符超详细总结

文章目录 1. 操作符的分类2. 二进制和进制转换2.1 2进制转10进制2.1.1 10进制转2进制数字 2.2 2进制转8进制和16进制2.2.1 2进制转8进制2.2.2 2进制转16进制 3. 原码、反码、补码4.移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&#xff1a;&、|、^、~6. 逗号表达式…

vue3 之 通用组件统一注册全局

components/index.js // 把components中的所组件都进行全局化注册 // 通过插件的方式 import ImageView from ./ImageView/index.vue import Sku from ./XtxSku/index.vue export const componentPlugin {install (app) {// app.component(组件名字&#xff0c;组件配置对象)…

图解 V8 执行 JS 的过程

本文来分享 V8 引擎执行 JavaScript 的过程 1. JS 代码执行过程 在说V8的执行JavaScript代码的机制之前&#xff0c;我们先来看看编译型和解释型语言的区别。 编译型语言和解释型语言 我们知道&#xff0c;机器是不能直接理解代码的。所以&#xff0c;在执行程序之前&#xf…

Java_栈_队列

文章目录 一、栈&#xff08;Stack&#xff09;1.概念2.栈的使用3.栈的模拟实现1、定义接口2、定义栈3、成员4、构造方法5、判断空间是否满 full6、入栈 push7、出栈 pop8、获取栈顶元素 peek9、获取栈中有效元素个数 size10、检测栈是否为空 empty完整代码 4.练习1、有效括号2…

GEE Colab——如何利用Matplotlib在colab中进行图形制作

在colab中绘制图表 笔记本的一个常见用途是使用图表进行数据可视化。Colaboratory 提供多种图表工具作为 Python 导入,让这一工作变得简单。 Matplotlib Matplotlib 是最常用的图表工具包,详情请查看其文档,并通过示例获得灵感。 线性图 线性图是一种常见的图表类型,用…

LabVIEW网络测控系统

LabVIEW网络测控系统 介绍了基于LabVIEW的网络测控系统的开发与应用&#xff0c;通过网络技术实现了远程的数据采集、监控和控制。系统采用LabVIEW软件与网络通信技术相结合&#xff0c;提高了系统的灵活性和扩展性&#xff0c;适用于各种工业和科研领域的远程测控需求。 随着…

哈希表(Hash Table)-----运用实例【通过哈希表来管理雇员信息】(java详解) (✧∇✧)

目录 一.哈希表简介&#xff1a; 实例介绍&#xff1a; 类的创建与说明&#xff1a; 各功能图示&#xff1a; 1.class HashTab{ }; 2. class EmpLinkedList{ }&#xff1b; 3. class Emp{ }&#xff1b; 4.测试&#xff1a; 运行结果&#xff1a; 最后&#xff0c;完整…

springboot微信小程序uniapp学习计划与日程管理系统

基于springboot学习计划与日程管理系统&#xff0c;确定学习计划小程序的目标&#xff0c;明确用户需求&#xff0c;学习计划小程序的主要功能是帮助用户制定学习计划&#xff0c;并跟踪学习进度。页面设计主要包括主页、计划学习页、个人中心页等&#xff0c;然后用户可以利用…

Java汽车销售管理

技术架构&#xff1a; springboot mybatis Mysql5.7 vue2 npm node 有需要该项目的小伙伴可以私信我你的Q。 功能描述&#xff1a; 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理 效果图&…