线程变量引发的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是通过线程池记录来减少线程的开销, 难道是现场复用引起的。 我有了一个大胆的猜测
- 系统管理员登录后办理业务使用A线程, 登录时设置了session到A线程的线程变量中
- 系统管理员完成业务操作后,因某个原因退出登录没有清空线程A的线程变量信息,也就是没有清空session
- 线程A回归线程池后再次被对账任务使用,此事后台任务从线程变量里取值就能错误获取线程变量里的记录信息
为了验证猜测我写了下面的一段测试代码模拟猜测场景。
- LocalThreadTest 实现Runable接口,并在其中设置一个线程变量
- 线程池有3个线程,并分配10个随机10秒的任务。
- 将第2个任务的线程设置一个线程变量
- 观察第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错乱的问题, 算是涨经验了
前一篇:线程池技术总结
相关文章:

线程变量引发的session混乱问题
最近不是在救火,就是在救火的路上。 也没什么特别可写的,今天记录下最近遇到的一个问题,个人觉得挺有意思, 待有缘人阅读 言归正传,售后反馈: 营业查询中付款方式为第三方支付的几条银行缴费,创…...
dockerfile与docker-compose解释及对比
Dockerfile 是一个文本文件,用于定义单个Docker镜像的构建过程和配置。它包含了一系列的指令,如FROM、RUN、COPY、CMD等,按照顺序执行这些指令来构建镜像。Dockerfile可以定义容器的基础镜像、安装依赖软件、拷贝文件、运行命令等操作。通过…...

数据库更换版本
目录 0.前言 1.官网下载MySQL 2.配置初始化文件my.ini 3.初始化MySQL 4.安装mysql服务并启动修改密码 5.配置环境变量编辑 0.前言 心累,为了完成实验,必须使用8.0版本导致我更新版本的时候,把sqlyog干崩溃了,什么版本不兼…...

Unity Meta Quest 一体机开发(九):【手势追踪】通过录制抓取手势实现自定义抓取姿势
文章目录 📕教程说明📕录制前的准备📕第一种录制方法(Hand Grab Pose Tool 场景)⭐在运行模式中确认录制⭐保存录制的手势,将物体做成 Prefab⭐在编辑阶段调整抓取手势🔍Fingers Freedom&#x…...

Git 简介及异常场景处理
一、简介 介绍Git之前,还得先介绍下 版本控制系统(VCS), 和它的发展历史 纵观版本控制系统的发展历史,广义上讲,版本控制工具的历史可以分为三代: 第一代 第一代版本控制系统被称为本地版本控…...

龙迅LT2611UX 四端口LVDS转HDMI(2.0)
1.描述: LT2611UX 四端口LVDS TO HDMI2.0。 LT2611UX是一款高性能得LVDS到HDMI2.0转换器得STB,DVD应用程序,LVDS输入可以配置单端口,双端口或者四端口,带有一个高速时钟通道,最多可运行三到四个高速数据…...

MySQL基础『数据类型』
✨个人主页: 北 海 🎉所属专栏: MySQL 学习 🎃操作环境: CentOS 7.6 阿里云远程服务器 🎁软件版本: MySQL 5.7.44 文章目录 1.数据类型一览2.整型2.1.INT2.2.BIT 3.浮点数3.1.FLOAT3.2.DECIMAL3…...

SQL手工注入漏洞测试(PostgreSQL数据库)-墨者
———靶场专栏——— 声明:文章由作者weoptions学习或练习过程中的步骤及思路,非正式答案,仅供学习和参考。 靶场背景: 来源: 墨者学院 简介: 安全工程师"墨者"最近在练习SQL手工注入漏洞&#…...

STM32单片机项目实例:基于TouchGFX的智能手表设计(1)项目介绍及GUI界面基础
STM32单片机项目实例:基于TouchGFX的智能手表设计(1)项目介绍及GUI界面基础 一、项目介绍 1.1方案提供 1.2主控选择 1.3硬件平台 1.4 开发环境 1.5 关于华清 二、GUI界面基础 2.1.1 嵌入式绘图系统 2.1.1 色彩格式 2.1.1帧缓冲区 …...

【超详细教程】基于html+js实现轮播图
轮播图是现代网页设计中常见的元素之一,它能够展示多张图片或内容,在有限的空间内循环播放,提升网页的视觉效果和用户体验。下面将以一个简单的网页轮播图为例,说明如何基于HTML和JavaScript实现。 1、R5Ai智能助手 chatgpt国…...

C++11(上)
统一的列表初始化 首先要说明: 这个列表初始化和类和对象那里的初始化列表不是一个概念. {} 初始化 在C98中, 标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定. 比如: C语言里面其实就是这样支持的, 所以可以认为C支持这样就是因为要兼容C. 在…...
web前端开发规范、HTML规范、JavaScript规范、style规范
MENU 前言目的 HTML规范用法规范注释规范 CSS规范用法规范书写顺序样式覆盖注释规范 JavaScript规范用法规范组件选项注释规范 命名规范目录命名图片命名文件命名方法命名样式命名常用词 工程结构目录构建代码风格 Git规范分支说明使用说明 相关连接 前言 目的 规范的目的是为…...

骨传导耳机会影响听力么?盘点骨传导耳机的好处与坏处都有哪些?
先说结论,使用骨传导耳机是不会影响听力的!并且由于骨传导耳机的特殊传声原理,相比于传统的入耳式耳机,骨传导耳机拥有更多的优点,下面带大家了解一下骨传导耳机的优点和缺点都有哪些。 一、骨传导耳机的优点是什么&a…...
前端与VR/AR:代码的魔法穿越
摘要: 前端开发者们,快戴上VR头盔,准备好进入未知的虚拟世界!本文将深度解析前端如何携手VR/AR技术,创造出更为奇妙的用户体验,同时以幽默的笔调诠释这场代码与虚拟现实的魔法邂逅。 引言 在前端的世界中…...

elment Loading 加载组件动态变更 text 值bug记录
先上效果图: 倒计时4分钟组件方法 // 倒计时 4分钟getSencond() {this.countDown 4分00秒this.interval setInterval(() > {this.maxTime--;let minutes Math.floor(this.maxTime / 60);let seconds Math.floor(this.maxTime % 60);minutes minutes < 10 ? 0 minu…...

Typora免费版安装教程(仅供学习)
目录 一、Typora简介二、Typora安装三、Typora补丁四、Typora使用体验五、总结 一、Typora简介 Typora是一款非常流行的Markdown编辑器,它能够将Markdown文本转化为漂亮的排版,并且支持实时预览。Typora具有简单易用的界面,使得用户可以轻松地…...

SSM项目实战-前端-添加分页控件-调正页面布局
1、Index.vue <template><div class"common-layout"><el-container><el-header><el-row><el-col :span"24"><el-button type"primary" plain click"toAdd">新增</el-button></el-…...

C语言从入门到实战——常用字符函数和字符串函数的了解和模拟实现
常用字符函数和字符串函数的了解和模拟实现 前言1. 字符分类函数2. 字符转换函数3. strlen的使用和模拟实现4. strcpy的使用和模拟实现5. strcat的使用和模拟实现6. strcmp的使用和模拟实现7. strncpy函数的使用8. strncat函数的使用9. strncmp函数的使用10. strstr的使用和模拟…...

nodejs+vue+elementui网上家电家用电器数码商城购物网站 多商家
基于vue.js的恒捷网上家电商城系统根据实际情况分为前后台两部分,前台部分主要是让用户购物使用的,包括用户的注册登录,查看公告,查看和搜索商品信息,根据分类定位不同类型的商品,将喜欢的商品加入购物车&a…...

32.768KHz时钟RTC晶振精度PPM值及频差计算
一个数字电路就像一所城市的交通,晶振的作用就是十字路口的信号灯,因此晶振的品质及其电路应用尤其关键。数字电路又像生命体,它的运行就像人身体里的血液流通,它不是由单一的某个器件或器件单元构成,而是由多个器件及…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...