记录一次并发问题的解决

news2024/12/24 0:02:29

并发问题的产生背景

该问题是在生产运行的过程中出现的。这个运行的项目是一个拉取第三方数据的一个服务,该服务会在拉取到数据之后直接将该数据直接插入到本地库,其中插入本地库的操作是调用的一个静态方法,静态方法对数据进行了多次数据处理,并且静态方法是异步执行的。当多个调用一起出现的时候,就相当于启动了多个线程去执行静态方法导致的并发。最终导致数据入库失败,并且程序抛错,具体错误信息如下:

1、错误一
Caused by: java.util.ConcurrentModificationException
	at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:761)
	at java.util.LinkedList$ListItr.next(LinkedList.java:696)
	at java.util.SubList$1.next(AbstractList.java:696)
2、错误二
java.lang.Index0utOfBoundsException: toIndex = 5
    at java.util.Sublist.<init>(Abstractlist.java:622) -[7:1.8.0 3337
    at java.util.sublist.sublist(abstractlist.java:750) -[?:1.8.0 333]

排查过程

上面的错误信息是截取了真实错误信息中的关键提示,隐藏了部分对代码的提示。我们根据错误信息找到了对应的代码。

private void processor() {
    beforeMethod();
    Context context = connector.request();
    afterEbancCache(context);
}

private void afterEbancCache(Context context) {
    ContextProcesserInsert.insertList(context)
}

public class ContextProcesserInsert {

    public static void insertList(Context context) {
        new Thread(() -> {
            insert(context);
        });
    }

    private void insert(Context context) {
        List<String> list = context.getList();
        // ... 省略几十行,主要计算开始和结束值,是用来分批插入数据库的
        for (xxx) {
            sqlMaperClient.insert(list.subList(startNum, endNum));
        }
    }

}

报错指向:sqlMaperClient.insert(list.subList(startNum, endNum));这一行代码。刚开始的是时候,观察下来这样的代码其实不会直接报错,并且报并发错误,最多报越界异常吧。不过越界异常确实在日志中出现了,所以可以理解,只是ConcurrentModificationException错误就相对来说不是那么容易理解。

ConcurrentModificationException错误的解释

其实看到这错,就已经很明确是list并发操作引起的了。不过这其实不是疑惑的点。不过还是先来看看这个错误的具体解释吧
异常出现的原因具体代码如下:
在这里插入图片描述
上面是我们错误具体报出这个错误的地方,我们可以看到,这个错误就是做了一件事情:在list进行下一个数据的操作之前会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0… 其实总结起来就一句话,就是我操作下一个元素之前,要去检查当前的list的长度是否有过变更,我记录的位置是否有出现错误。
单线程执行的时候,私有了对应的对象,不会出现list长度被更改的情况,但是并发执行,可能就将操作下一个数据变成了操作下下个数据,从而导致,整个list的最终记录出现问题。所以需要检查这个问题,然后发现就抛出错误。

看到这样的解释,其实和上面的表现已经很吻合了。疑惑的地方只有一个,上面案例来说并发不会并发到insert这个方法来,因为insert是实例方法。而且整个代码下来,并没有真正去修改list数据或者list长度的地方。

还原失败

为了能够找到足够的证据,证明上面的错误是并发出现的,我们做了很多的本地测试。该测试主要是为了能够更有效的保证处理并发问题。
期间我们做了一下几件事来验证

  • 使用编写代码的形式,启动1000个线程来跑程序
  • jmeter - 1000线程并发测试
  • 断点线程干预

以上操作均没有得到想要的效果,最终还原失败告终

解决方案

根据上面的推测,我们没有得到最有力的证据,不过大致是看明白了怎么去解决。其中真正引起这个并发的原因应该就是最开始的静态方法引起的,该静态方法经过多层级调用,对list操作,导致最终并发报错。其中静态方法的原因,将list对应的数据变成了公共变量,不再是私有变量。
最终将list变量继续变成私有变量就能解决这个问题,于是添加了以下代码

public static void insertList(Context context) {
    SerialCloneUtils.deepClone(context);
    new Thread(() -> {
        insert(context);
    });
}

public class SerialCloneUtils {

    /**
     * 使用ObjectStream序列化实现深克隆
     *
     * @return Object obj
     */
    public static <T extends Serializable> T deepClone(T t) {
        InputStream bin = null;
        ObjectInputStream in = null;
        ByteArrayOutputStream bout = null;
        ObjectOutputStream out = null;
        try {
            bout = new ByteArrayOutputStream();
            out = new ObjectOutputStream(bout);
            out.writeObject(t);
            bin = new ByteArrayInputStream(bout.toByteArray());
            in = new ObjectInputStream(bin);
            return (T) (in.readObject());
        } catch (Exception e) {
            logger.error("深克隆对象出现问题,报错信息:" + e.getMessage());
        } finally {
            if (bin != null) {
                try {
                    bin.close();
                } catch (Exception e) {
                    logger.error("深克隆对象, ByteArrayInputStream关闭异常,报错信息:" + e.getMessage());
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    logger.error("深克隆对象, ObjectInputStream关闭异常,报错信息:" + e.getMessage());
                }
            }
            if (bout != null) {
                try {
                    bout.close();
                } catch (Exception e) {
                    logger.error("深克隆对象, ByteArrayOutputStream关闭异常,报错信息:" + e.getMessage());
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    logger.error("深克隆对象, ObjectOutputStream关闭异常,报错信息:" + e.getMessage());
                }
            }
        }
        return t;
    }

}

完美解决!

以上解决办法会再次出现并发吗?

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

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

相关文章

携程Apollo配置中心架构介绍

俗话说”麻雀虽小&#xff0c;五脏俱全“&#xff0c;有人说想看开源源码却不知道什么好&#xff0c;事实上&#xff0c;那些流行多年&#xff0c;广受好评的开源工程都是很值得一读的。今天我们介绍Apollo配置中心的基本情况&#xff0c;之所以介绍这个&#xff0c;主要是因为…

在线地图持续进化,BAT技术“鲜”发制人

配图来自Canva可画 眼下&#xff0c;在线地图正在成为智能穿戴、物流运输、旅游度假等诸多领域的“基础设施”&#xff0c;尤其是自动驾驶、车路协同等汽车细分赛道越来越重视在线地图的导入。 得益于此&#xff0c;在线地图市场持续走向火热。华经产业研究院数据显示&#x…

linux基础学习-ssh基础

ssh基础 通过SSH客户端我们可以连接到运行了SSH服务器的远程机器上 SSH客户端是一种使用Secure Shell(SSH)协议连接到远程计算机的软件程序目前较为可靠&#xff0c;专门为远程登录会话和其他网络服务提供安全性的协议 利用SSH协议可以有效防止远程管理过程中的信息泄露提供SS…

行业级开源无人机目标追踪,高空助力抓贼!

活久见&#xff01;成都一高楼惊险无人机抓小偷视频中危险动作&#xff0c;请勿模仿&#xff01; 本次实验中我们使用的是Prometheus 600&#xff08;P600&#xff09;行业级无人机研发平台&#xff08;此平台适用于无人机行业应用开发与室外环境下的无人机算法验证&#xff0…

White Rose设计与架构的想法分享

在七牛云校园黑客马拉松中&#xff0c;一款设计优秀、逻辑清晰的白板作品脱颖而出&#xff0c;获得第二名的好成绩&#xff0c;这就是来自郑州大学Since团队的White Rose白板&#xff0c;以下是他们的设计和架构分享。 一、前言 White Rose是参加七牛云hackathon比赛的作品&am…

【】Fate单机部署及代码调试全流程ongoing

这里写自定义目录标题Fate单机部署及代码调试全流程一、安装Linux系统或者虚拟机-Linux系统1、先装虚拟机2、在虚拟机上安装Ubuntu系统二、FATE单机部署并PyCharm如何连接远程服务器的docker容器进行运行和调试代码【整体未成功&#xff0c;可跳过】三、Ubuntu系统上安装anacon…

谈谈什么是才是InnoDB解决幻读的最佳方案

本文会分享一下MySQL的InnoDB引擎下的事务幻读问题与解决方案--LBCC&MVCC。经过好几天的熬夜通宵&#xff0c;终于把这部分的内容捋清楚了。至于为什么说是InnoDB呢&#xff1f;因为MyISAM引擎是不支持事务的。 事务 概念 一个事情由n个单元组成&#xff0c;这n个单元在…

Apache 虚拟主机里的 ServerName 指令

术语虚拟主机(Virtual host)是指在一台机器上运行多个网站&#xff08;例如 company1.example.com 和 company2.example.com&#xff09;的做法。 虚拟主机可以是“基于 IP”的&#xff0c;这意味着每个网站都有不同的 IP 地址&#xff0c;也可以是“基于名称的”&#xff0c;…

Springboot集成Neo4j

一、概述 1.为什么图形数据库&#xff1f; 生活在一个互联的世界中&#xff0c;大多数领域需要处理丰富的连接集以了解真正发生的事情。通常&#xff0c;我们发现项目之间的联系与项目本身一样重要。 虽然现有的关系数据库可以存储这些关系&#xff0c;但它们通过昂贵的JOIN操作…

微服务框架 SpringCloud微服务架构 服务异步通讯 50 消息可靠性 50.3 消费者消息确认

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 服务异步通讯 文章目录微服务框架服务异步通讯50 消息可靠性50.3 消费者消息确认50.3.1 消费者确认50 消息可靠性 50.3 消费者消息确认 50…

OTA前装搭载率逼近50%,哪些供应商正在领跑细分赛道

智能汽车的OTA&#xff0c;正在进入新发展周期。 早期的OTA&#xff0c;主要围绕座舱信息娱乐、T-BOX及少部分车内其他ECU&#xff0c;主要目的是修复软件Bug以及改进用户体验&#xff0c;降低整车的召回成本。这个阶段&#xff0c;OTA对应的整车电子架构还是以传统的分布式EC…

前端上传文件或者上传文件夹

文档 https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input 上传文件夹&#xff0c;主要的参数webkitdirectory 浏览器上传文件夹&#xff0c;浏览器会弹出询问窗口 兼容性 https://caniuse.com/?searchwebkitdirectory 代码如下 <!-- 选择文件 -->…

【Anime.js】——Anime.js源码之引擎的理解

一、Anime.js整体结构 Anime.js的强大之处在于代码量非常少&#xff0c;但功能却非常强大。让我们一起来探索Anime.js源码的核心吧~ Anime.js之所以能如此强大主要是因为它的代码结构设计的非常巧妙合理&#xff0c;所以我们想要掌握Anime.js的核心&#xff0c;首先我们要了解…

CUDA入门和网络加速学习(四)

0. 简介 最近作者希望系统性的去学习一下CUDA加速的相关知识&#xff0c;正好看到深蓝学院有这一门课程。所以这里作者以此课程来作为主线来进行记录分享&#xff0c;方便能给CUDA网络加速学习的萌新们去提供一定的帮助。 1. Cublas概念 cuBLAS是一个BLAS的实现&#xff0c;…

低代码常见场景【下】|行业示例

全文 3131 字 阅读时间约 9 分钟 本文首发于码匠技术博客 目录 低代码的行业示例 低代码在业务用例中的优势 关于码匠 阅读完上一篇文章后&#xff08;低代码用例【上】&#xff5c;如何解决业务问题&#xff09;&#xff0c;想必您已经对低代码的通用用例以及低代码如何解…

PolarDB-X源码解读:DDL的一生(下)

概述 在《DDL的一生&#xff08;上&#xff09;》中&#xff0c;我们以添加全局二级索引为例&#xff0c;从DDL开发者的视角介绍了如何在DDL引擎框架下实现一个逻辑DDL。在本篇&#xff0c;作者将从DDL引擎的视角出发&#xff0c;向读者介绍DDL引擎的架构、实现&#xff0c;以…

应用于高速收发模块的并行光学WDM波分光学技术

光模块的传输距离分为短距、中距、长距。通常短距离传输是指2km以下的传输距离&#xff0c;中距为10-20km。≥30km的则为长距离传输。根据不同的传输距离&#xff0c;光模块类型分为SR&#xff08;100m&#xff09;、DR&#xff08;500m&#xff09;、FR&#xff08;2km&#x…

实战三十二:基于knn算法的用户购物消费预测代码+数据

K近邻算法通过计算被分类对象与训练集对象之间的距离,确定其k个临近点,然后使用这k个临近点中最多的分类作为分类结果。 如上图,当K=3时,它会被分类为 Class B。因为K=3时,3个临近点里有2个是B类的。 同理,K=7时它会被分类为 Class A,因为K=7时,7个临近点里4个是A类的…

Clion开发stm32之下载程序记录

Clion开发stm32之下载程序 前提条件 安装openocd安装clion安装arm-gcc环境安装 MinGW(或Mysys2) 注意事项 !!! 开发路径必须要选择英文路径(中文路径会编译不通过的) 说明 这里为了在之后的项目里面使用配置文件。我们需要到openocd提供的board目录下添加自己的配置信息(…

gcc编译

gcc编译可执行程序有4个步骤&#xff1a;预处理、编译、汇编、链接。编译阶段消耗时间、系统资源最多。 从源文件hello.c到目标可执行文件hello&#xff0c;可以按照下面的执行命令&#xff0c;一步一步生成。 gcc -E hello.c -o hello.i gcc -S hello.i -o hello.s gcc -c he…