线程变量引发的session混乱问题

最近不是在救火,就是在救火的路上。 也没什么特别可写的,今天记录下最近遇到的一个问题,个人觉得挺有意思, 待有缘人阅读

言归正传,售后反馈: 营业查询中付款方式为第三方支付的几条银行缴费,创建操作员和修改操作员为系统操作员,系统操作员一般只用于系统配置,不会用于处理业务, 这类异常数据会导致月底与财务报表不正确

看到这个问题的时候第一感觉就是有点蒙, 在我们的系统中的配置操作员和业务操作员是分开的。银行缴费的操作员记录的是指定的业务操作员

查看系统日志,发现日志中有问题交易流水120231116205337hF544的记录信息, 从日志分析,问题数据不是通过客户充值产生的数据,而是银行对账产生的数据。 所谓对账就是指本系统的缴费和银行系统的缴费做对比,对方有我方没有则补数据,对方没有我方有则冲正数据, 双方都有则按照银行的数据修复成一直。 通过日志定位问题数据都是对方有我方没有而补录的数据。

2023-11-17 09:41:00,997 INFO  [com.bank.server.checkAccount.CheckAccountJob] Checking detail account...
2023-11-17 09:41:01,001 INFO  [com.bank.server.checkAccount.CheckSingleContext] boss中不存在关于流水号120231116205337hF544的付款记录,重做对应交易
2023-11-17 09:41:01,004 INFO  [com.bank.server.checkAccount.CheckSingleContext] there is not detail that flowNo is 120231116205337hF544,transaction again

查看补录缴费程序的实现,公司内部框架中的持久化处理默认会设置表数据的创建操作员(operator)为登录session中的操作员信息。对账是后台任务处理的,后台任务没有操作员登录,没有操作员登录那么肯定就没有session. 处理到这儿,我开始有点儿见鬼的感觉,想不明白为什么一个后台任务突然有了session,有了登录信息,

private static void setCreateInfo(ApplicationSession session, AbstractSystemModel entity) {Operator createOperator = null;if (session == null || session.getValue("operator") == null) {createOperator = new Operator();}else {createOperator = (Operator) session.getValue("operator");}if (entity.getCreateOperator() == null || entity.getCreateOperator().getId() == null) {entity.setCreateOperator(createOperator);}if (entity.getCreateDate() == null) {entity.setCreateDate(new Timestamp(System.currentTimeMillis()));}}

统计稽核问题数据发现后台对账任务记录信息也很奇怪,有修改操作员记录的数据表示对账任务有session,没有修改操作员的记录表示此时对账任务没有session, 这个现象说明当前问题是偶然现象

当操作员登录后会在线程变量里缓存session信息,用于快速获取登录信息。从上面的数据库分析,执行对账的后台任务有的时候有session有的时候没有session .这个现象很像是线程变量增加session后没有清空引起的。

我们知道Jboss是通过线程池记录来减少线程的开销, 难道是现场复用引起的。 我有了一个大胆的猜测

  1. 系统管理员登录后办理业务使用A线程, 登录时设置了session到A线程的线程变量中
  2. 系统管理员完成业务操作后,因某个原因退出登录没有清空线程A的线程变量信息,也就是没有清空session
  3. 线程A回归线程池后再次被对账任务使用,此事后台任务从线程变量里取值就能错误获取线程变量里的记录信息

为了验证猜测我写了下面的一段测试代码模拟猜测场景。

  1. LocalThreadTest 实现Runable接口,并在其中设置一个线程变量
  2. 线程池有3个线程,并分配10个随机10秒的任务。 
  3. 将第2个任务的线程设置一个线程变量
  4. 观察第2个任务的线程被再次使用的时候线程变量是否存在
package com.thread.localthread;import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class LocalThreadTest implements Runnable {private static final ThreadLocal<String> LOCAL_SESSION = new ThreadLocal<String>();private Integer index;public LocalThreadTest(Integer i){index=i;}public static void main(String[] args) {ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);for (Integer i = 0; i < 10; i++) {fixedThreadPool .execute(new LocalThreadTest(i));}fixedThreadPool.shutdown();}@Overridepublic void run() {if(index==2){LOCAL_SESSION.set("session is"+Thread.currentThread().getName());}System.out.println(LOCAL_SESSION.get());Random random = new Random();int randomNumber = random.nextInt(4) + 1;try {Thread.sleep(randomNumber*1000);} catch (InterruptedException e) {e.printStackTrace();}}
}

执行结果发现和想的一样当线程回归线程池后。再次使用的时候线程变量仍会被线程持有。并不会被清除。

null
null
session ispool-1-thread-3
null
session ispool-1-thread-3
null
null

查看代码还有一个疑问没有解决,开启对账任务的时候是通过start()方法来启动线程的,而不是run方法(run()方法不会新创建线程)。 如果主线程中使用ThreadLocal记录线程变量, 当使用start()方法运行线程时是真正创建一个子线程。 对于线程变量ThreadLocal对象来说,子线程不会持有主线程中ThreadLocal的信息。这么来看,对账处理中ApplicationSession 应该也没有session才对。

public void billingServiceStart(int processInstanceId,long billingServiceInstanceId) {try {//.....略部分代码AbstractBillingServiceContext context = (AbstractBillingServiceContext) BeanFactoryHolder.get().getBean(contextName);context.setBillingServiceInstance(billingServiceInstance);Thread t = new Thread(context);t.start();} catch (Exception e) {logger.error("case:", e);BillingServiceInstance bsi = billingServiceInstanceDao.queryBillingServiceInstance(billingServiceInstanceId);bsi.setProcessStatus(ServiceProcessStatus.ERROR_FINISHED);bsi.setInfoStr(e.toString() + ":" + e.getMessage());bsi.setEndDate(new Date());billingServiceInstanceDao.modifyBillingServiceInstance(bsi);}}

再进一步分析ApplicationSessionHolder的代码才解决了自己的疑惑。 ApplicationSessionHolder对象中使用的线程变量是InheritableThreadLocal对象,InheritableThreadLocal是ThreadLocal 的子类,两者的区别是InheritableThreadLocal创建时可以获取主线程的线程变量值,

public final class ApplicationSessionHolder
{private static final InheritableThreadLocal<ApplicationSessionHolder> LOCAL_SESSION = new InheritableThreadLocal();private boolean clear;private ApplicationSession session;private ApplicationSessionHolder(ApplicationSession session){this.session = session;}
}

 

public class InheritableThreadLocal<T> extends ThreadLocal<T> {}

至此定位问题原因, 第一次遇到session错乱的问题, 算是涨经验了

前一篇:线程池技术总结

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

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

相关文章

ai绘画Midjourney绘画提示词Prompt教程

一、Midjourney绘画工具 SparkAi【无需魔法使用】&#xff1a; SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01;本系统使用NestjsVueTypescript框架技术&#xff0c;持续集成AI能力到…

成为AI产品经理——模型稳定性评估(PSI)

一、PSI作用 稳定性是指模型性能的稳定程度。 上线前需要进行模型的稳定性评估&#xff0c;是否达到上线标准。 上线后需要进行模型的稳定性的观测&#xff0c;判断模型是否需要迭代。 稳定度指标(population stability index ,PSI)。通过PSI指标&#xff0c;我们可以获得不…

Windows使用Redis

Windows使用Redis 前言一、安装wsl2&#xff08;Windows Subsystem for Linux&#xff09;二、在wsl中下载并安装Redis一主二仆哨兵模式 前言 主要是记录一下&#xff0c;免得自己忘了。 一、安装wsl2&#xff08;Windows Subsystem for Linux&#xff09; Redis官网中说&…

GitHub上1.5K标星的QA和软件测试学习路线图

​最近在GitHub上发现一个项目&#xff0c;项目描述了作为QA工程师&#xff0c;进行软件测试技能提升时的&#xff0c;建议的软件测试学习顺序图​。 虽然2021年起就不再更新了&#xff0c;但是居然有1.5K的​星。 整个项目有两个部分​&#xff1a; ​1.QA和软件测试学习顺序…

craco + webpack 4 升 5

craco webpack 4 升 5 更新包版本尝试build升级其他依赖库使用process插件打印进度信息到底需要多少内存分析构建产出添加 splitChunk总结记录一些好文章&#xff1a; 我的项目使用 craco react 开发 我的 package.json {// ......"dependencies": {"ant-desi…

沐风老师3DMAX拼图随机生成器Puzzle建模工具使用教程

3DMAX拼图随机生成器Puzzle建模工具使用教程 3DMAX拼图随机生成器Puzzle&#xff0c;是一款用MAXScript脚本语言开发的3dsMax小工具&#xff0c;可以随机创建可编辑多边形3D拼图对象。可批量生成阵列。 【适用版本】 3dMax2015-2024&#xff08;不仅限于此范围&#xff09; 【…

前端大文件上传webuploader(react + umi)

使用WebUploader还可以批量上传文件、支持缩略图等等众多参数选项可设置&#xff0c;以及多个事件方法可调用&#xff0c;你可以随心所欲的定制你要的上传组件。 分片上传 1.什么是分片上传 分片上传&#xff0c;就是将所要上传的文件&#xff0c;按照一定的大小&#xff0c;将…

Langchain-Chatchat的安装过程

参考&#xff1a;LLMs之RAG&#xff1a;LangChain-Chatchat(一款中文友好的全流程本地知识库问答应用)的简介(支持 FastChat 接入的ChatGLM-2/LLaMA-2等多款主流LLMs多款embe_一个处女座的程序猿的博客-CSDN博客 1、安装过程中出现了 GPU驱动版本 是11.8 而 python -c "…

探索人工智能领域——每日20个名词详解【day8】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

学习使用三个命令实现在腾讯云服务器TencentOS Server 3.1或者CentOS 8上安装ffmpeg

学习使用三个命令实现在腾讯云服务器TencentOS Server 3.1或者CentOS 8上安装ffmpeg Error: Unable to find a match: ffmpeg添加RPMfusion仓库安装SDL安装ffmpeg执行命令测试 Error: Unable to find a match: ffmpeg 添加RPMfusion仓库 yum install https://download1.rpmfus…

[Java学习日记]多线程练习、线程池

目录 一.案例&#xff1a;五个人抢红包 二.案例&#xff1a;两个抽奖池抽奖 三.案例&#xff1a;两个抽奖池抽奖&#xff1a;获取线程运行的结果 四.线程池&#xff1a;用来存放线程&#xff0c;避免多次重复创建线程 五.自定义线程池 六.最大并行数与线程池大小 一.案例&…

【EI会议征稿】第九届电气、电子和计算机工程研究国际学术研讨会 (ISAEECE 2024)

第九届电气、电子和计算机工程研究国际学术研讨会 (ISAEECE 2024) 2024 9th International Symposium on Advances in Electrical, Electronics and Computer Engineering 第九届电气、电子和计算机工程研究国际学术研讨会(ISAEECE 2024&#xff09;将于2024年3月1-5日在南京…

AWS攻略——创建VPC

文章目录 创建一个可以外网访问的VPCCIDR主路由表DestinationTarget 主网络ACL入站规则出站规则 子网创建EC2测试连接创建互联网网关&#xff08;IGW&#xff09;编辑路由表 知识点参考资料 在 《AWS攻略——VPC初识》一文中&#xff0c;我们在AWS默认的VPC下部署了一台可以SS…

鸿蒙4.0开发笔记之ArkTS装饰器语法基础之监听者模式@Watch案例讲解(十四)

1、Watch定义 Watch实际是指状态变量更改通知。如果开发者需要关注某个状态变量的值是否改变&#xff0c;可以使用Watch为状态变量设置回调函数&#xff08;监听函数&#xff09;。 Watch用于监听状态变量的变化&#xff0c;当状态变量变化时&#xff0c;Watch的回调方法将被…

隐写术和人工智能

在一项新的研究中&#xff0c;人工智能对齐研究实验室 Redwood Research 揭示了大型语言模型 (LLM) 可以掌握“编码推理”&#xff0c;这是一种隐写术形式。 这种有趣的现象使得大型语言模型能够以人类读者无法理解的方式巧妙地将中间推理步骤嵌入到生成的文本中。 大型语言…

C++初阶 | [七] string类(上)

摘要&#xff1a;标准库中的string类的常用函数 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c; 但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP(面向对象)的思想&#…

03、pytest初体验

官方实例 # content of test_sample.py def func(x):return x 1def test_ansewer():assert func(3) 5步骤解释 [100%]指的是所有测试用例的总体进度&#xff0c;完成后&#xff0c;pytest显示一个失败报告&#xff0c;因为func(3)没有返回5 注意&#xff1a;你可以使用ass…

【滤波第二期】中值滤波的原理和C代码

中值滤波是一种非线性数字滤波技术&#xff0c;主要应用于信号处理和图像处理领域&#xff0c;用于减小信号中的噪声和离群值。中值滤波的核心思想是通过计算一组数据点的中间值&#xff0c;以抑制脉冲噪声等离群值的影响&#xff0c;从而实现信号的平滑处理。 1&#xff0c;中…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之linux存储管理(5)》(21)

《Linux操作系统原理分析之linux存储管理&#xff08;5&#xff09;》&#xff08;21&#xff09; 6 Linux存储管理6.6 Linux 物理空间管理6.6.1 Linux 物理内存空间6.6.2 物理页面的管理6.6.3 空闲页面管理——buddy 算法 6.7 内存的分配与释放6.7.1 物理内存分配的数据结构 6…

canvas绘制小丑

说明&#xff1a; 借鉴博主基于canvas绘制一个爱心(10行代码就够了) - 掘金 (juejin.cn) 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…