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

2023-12-05

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

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

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

查看系统日志,发现日志中有问题交易流水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();
    }

    @Override
    public 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错乱的问题, 算是涨经验了

前一篇:线程池技术总结

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程变量引发的session混乱问题 的相关文章

随机推荐

  • 浅浅的聊一下时间窗口及其应用场景

    一 前言 时间窗口在限流 分布式 ID 的生成方面都有很多应用 这一篇主要目的是弄清楚怎么最好的实现这个功能 时间窗口的应用很多 可以用于统计和监控 也可以用于限流和流量控制 或者在指定窗口里面做实时计算 ID 生成等业务处理等 二 原理
  • 【计算机毕设选题推荐】基于Java的生活超市系统的设计与实现

    精彩专栏推荐订阅 在下方主页 作者主页 计算机毕设木哥 文章目录 一 项目介绍 二 开发环境 三 系统展示 四 代码展示 五 项目总结 font color fe2c24 大家可以帮忙点赞 收藏 关注 评论啦 一 项目介绍 在当前数字化和信
  • C言C语之数据类型、运算符与表达式

    01 C语言的数据类型 数据类型一般是按照被定义变量的性质 表现形式 占据的内存大小 构造特点来进行划分的 C语言中 数据类型分为 基本数据类型 构造数据类型 指针类型 空类型四大类 基本类型又分为 整型 字符型 实型 或浮点型 枚举类型
  • 简单的配音软件有哪些?告诉你怎么用机器实现配音

    爱看电视剧 甄嬛传 的小伙伴们应该都对这几句经典台词有深刻的印象吧 三阿哥他 又长高了 粉色娇嫩 你如今几岁了 臣妾做不到啊 皇额娘她推了熹娘娘 她推了熹娘娘 惟愿逆风如解意 容易莫摧残 这福气给你要不要啊 有时候我一看见这些台词 脑海里仿
  • 国产手机品牌放弃低端市场,山寨手机卷土重来,价格低至80元

    国产手机品牌纷纷哀叹手机不好卖 相比起品牌手机的哀叹 山寨手机则没有怨天尤人 而是积极主动的寻找市场机会 比国产手机品牌更能在特殊的市场环境中求生存 据某电商平台的数据 山寨手机如今仍然占有不小的市场份额 他们主要以做低端机为主 价格非常实
  • 图片编辑软件哪个好?一键美化

    图片编辑软件是一种功能强大的工具 可以帮助使用者对数字图像进行各种编辑和修饰 这些软件提供了各种工具和功能 使用者可以对图像进行裁剪 调整亮度 对比度 色彩平衡 应用滤镜效果 添加文字和形状等操作 通过这些图片编辑修改文字软件 可以大大增强
  • 基于SpringBoot实现功能最全电影购票与信息资讯平台

    作者简介 Java领域优质创作者 CSDN博客专家 CSDN内容合伙人 掘金特邀作者 阿里云博客专家 51CTO特邀作者 多年架构师设计经验 腾讯课堂常驻讲师 主要内容 Java项目 Python项目 前端项目 人工智能与大数据 简历模板
  • 【电子学会】2023年05月Python三级试卷

    青少年软件编程 Python 等级考试试卷 三级 分数 100 题数 38 一 单选题 共25题 每题2分 共50分 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
  • 电感耦合等离子体(ICP)-科学指南针

    ICP 即 电感耦合等离子体 Inductively Coupled Plasma 一般指电离度超过0 1 被电离了的气体 这种气体不仅含有中性原子和分子 而且含有大量的电子和离子 且电子和正离子的浓度处于平衡状态 从整体来看是中性的 有时
  • node.js学习笔记——内部模块、自定义模块的导入和使用方式

    文章目录 前文提要 内部模块 fs模块 导入方式 fs readFile fs writeFile path模块
  • VSCode设置中文

    https www somode com softjc 30980 html
  • 卷积核的基本概况

    一 什么是卷积核 在数学上 卷积核的标准定义是 两个函数在反转和移位后的乘积的积分 其中 函数g一般称为 过滤器 filters 函数f指的是 信号 图像 在卷积神经网络里 卷积核其实就是一个过滤器 但在深度学习里 它不做反转 而是直接 执
  • ChatGPT发布一年后,搜索引擎的日子还好吗?

    导读 生成式AI 搜索引擎的终结者还是进化加速器 ChatGPT发布刚刚一年 互联网世界已经换了人间 2023年 以ChatGPT和大模型为代表的生成式AI浪潮对全球互联网 云计算 人工智能领域都带来巨大冲击 而且生成式AI在各行各业的应用
  • 【GESP】2023年09月Python一级试卷

    2023年GESP09月认证Python一级试卷 分数 100 题数 27 一 单选题 共15题 每题2分 共30分 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
  • 接口自动化测试用例

    1 接口文档 根据开发 产品的接口文档 以及评审 进行设计接口测试用例 它不像UI测试 有个界面 对于简单的系统 需求文档不提供也能覆盖所有功能 接口测试虽说可以抓包 但抓包无法覆盖所有接口和字段 必须要求提供详细接口文档 甚至可以辅助扣代
  • pytorch 给定概率分布的张量,如何利用这个概率进行重复\不重复采样?

    在 PyTorch 中 可以使用 torch distributions Categorical 来基于给定的概率分布进行采样 下面是一个示例 import torch import torch distributions as dist
  • 碳复合材料的导热测量-科学指南针

    30年前碳复合材料就被开发出来 用于取代高性能军用飞行器引擎上的金属部件 现在这种材料也被用在民用飞行器 刹车制动 赛车刹车片 真空炉部件 化学反应器和其它工业 碳复合材料具有诸多特性 比如质地轻 化学惰性 高温下强度高 比强度高于耐热合金
  • 《OCAD光学系统自动设计程序》好书分享

    OCAD是一款具有鲜明工程化特色的中国光学自动设计软件 程序重点在于可以首先进行复杂光学系统的初始布局 进行设计前期的总体方案设计 可以对各类典型光学系统自动进行初始结构设计 作为一款光学系统辅助设计软件 SEEOD以光的直线传播 数学几何
  • OptiSystem应用:脉冲内拉曼散射对高阶光孤子的衰变

    本课程演示了受激拉曼散射对短孤子脉冲的影响 布局及其全局参数如图1和图2所示 图1 光路布局 图2 全局参数设置 图3 脉冲生成器设置 非线性色散光纤组件的参数如图4所示 该布局模拟了高阶孤子脉冲的传播 脉冲宽度 FWHM 为450 62f
  • 线程变量引发的session混乱问题

    最近不是在救火 就是在救火的路上 也没什么特别可写的 今天记录下最近遇到的一个问题 个人觉得挺有意思 待有缘人阅读 言归正传 售后反馈 营业查询中付款方式为第三方支付的几条银行缴费 创建操作员和修改操作员为系统操作员 系统操作员一般只用于系