多租户 TransmittableThreadLocal 线程安全问题

在一个多租户项目中,用户登录时,会在自定义请求头拦截器AsyncHandlerInterceptor将该用户的userId,cstNo等用户信息设置到TransmittableThreadLocal中,在后续代码中使用.代码如下:

HeaderInterceptor 请求头拦截器


public class HeaderInterceptor implements AsyncHandlerInterceptor
{@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{if (!(handler instanceof HandlerMethod)){return true;}SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));SecurityContextHolder.setCstNoKey(ServletUtils.getHeader(request, SecurityConstants.CST_NO));String token = SecurityUtils.getToken();if (StringUtils.isNotEmpty(token)){LoginUser loginUser = AuthUtil.getLoginUser(token);if (StringUtils.isNotNull(loginUser)){AuthUtil.verifyLoginUserExpire(loginUser);SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);}}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception{SecurityContextHolder.remove();}
}

SecurityContextHolder


public class SecurityContextHolder
{private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();public static void set(String key, Object value){Map<String, Object> map = getLocalMap();map.put(key, value == null ? StringUtils.EMPTY : value);}public static String get(String key){Map<String, Object> map = getLocalMap();return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));}public static <T> T get(String key, Class<T> clazz){Map<String, Object> map = getLocalMap();return StringUtils.cast(map.getOrDefault(key, null));}public static Map<String, Object> getLocalMap(){Map<String, Object> map = THREAD_LOCAL.get();if (map == null){map = new ConcurrentHashMap<String, Object>();THREAD_LOCAL.set(map);}return map;}public static void setLocalMap(Map<String, Object> threadLocalMap){THREAD_LOCAL.set(threadLocalMap);}public static void setCstNoKey(String cstNoKey){set(SecurityConstants.CST_NO, cstNoKey);}public static String getCstNo(){return Convert.toStr(get(SecurityConstants.CST_NO), null);}public static Long getUserId(){return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L);}public static void setUserId(String account){set(SecurityConstants.DETAILS_USER_ID, account);}public static String getUserName(){return get(SecurityConstants.DETAILS_USERNAME);}public static void setUserName(String username){set(SecurityConstants.DETAILS_USERNAME, username);}public static String getUserKey(){return get(SecurityConstants.USER_KEY);}public static void setUserKey(String userKey){set(SecurityConstants.USER_KEY, userKey);}public static String getPermission(){return get(SecurityConstants.ROLE_PERMISSION);}public static void setPermission(String permissions){set(SecurityConstants.ROLE_PERMISSION, permissions);}public static void remove(){THREAD_LOCAL.remove();}}

在业务层通过调用多线程方法时,分别在主线程和子线程中输出客户编号,伪代码如下:

public void listMasterThread() {log.info(">>>>>>>>>>>主线程{}>>>>>", SecurityContextHolder.getCstNo());
CompletableFuture.supplyAsync(() -> this.savleThread(),threadPoolConfig.threadPool())}public void savleThread(){log.info(">>>>>>>>>>>子线程{}>>>>>", SecurityContextHolder.getCstNo());
}

问题复现:
客户编号为85的员工先登录(第一次登录)并调用该方法时,输出如下:

>>>>>>>>>>>主线程85 >>>>>
>>>>>>>>>>>子线程85 >>>>>

接着客户编号82的员工接着登录(第二次登录)并调用时,输出:

>>>>>>>>>>>主线程82 >>>>>
>>>>>>>>>>>子线程85 >>>>>

问题就来了,第二次登录时,主线程获取的客户编号正确,但是子线程获取的客户编号是上一次登录,并且多调用几次之后,子线程的输出也正常了.

下面是TransmittableThreadLocal.get()方法源码,最终调用的是ThreadLocal.get()
在这里插入图片描述
百度了一下TransmittableThreadLocal 用于解决 “在使用线程池会缓存线程的组件情况下传递ThreadLocal”问题的.

到这里就很疑惑,明明用的就是 TransmittableThreadLocal但是为何还会有此问题,经过折腾

解决方法添加TtlExecutors.getTtlExecutor()

	// threadPoolConfig.threadPool() 参数为指定的线程池TtlExecutors.getTtlExecutor(threadPoolConfig.threadPool());

纠正后的伪代码如下,

public void listMasterThread() {log.info(">>>>>>>>>>>主线程{}>>>>>", SecurityContextHolder.getCstNo());TtlExecutors.getTtlExecutor(threadPoolConfig.threadPool());
CompletableFuture.supplyAsync(() -> this.savleThread(),threadPoolConfig.threadPool())}public void savleThread(){log.info(">>>>>>>>>>>子线程{}>>>>>", SecurityContextHolder.getCstNo());
}

具体解决原理不知!!!

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

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

相关文章

什么是MTU(Maximum Transmission Unit)?

热门IT课程【视频教程】-华为/思科/红帽/oraclehttps://xmws-it.blog.csdn.net/article/details/117297837?spm1001.2014.3001.5502 最大传输单元MTU&#xff08;Maximum Transmission Unit&#xff0c;MTU&#xff09;&#xff0c;是指网络能够传输的最大数据包大小&#x…

太阳能供电井盖-物联网智能井盖监测系统-旭华智能

在这个日新月异的科技时代&#xff0c;城市的每一个角落都在悄然发生变化。而在这场城市升级的浪潮中&#xff0c;智能井盖以其前瞻性的科技应用和卓越的安全性能&#xff0c;正悄然崭露头角&#xff0c;变身马路上的智能“眼睛”&#xff0c;守护城市安全。 传统的井盖监测系统…

协议-http协议-基础概念03-http状态码-http特点-http性能-压缩和分块传输-范围请求

参考来源&#xff1a; 极客时间-透视HTTP协议(作者&#xff1a;罗剑锋)&#xff1b; 01-状态码分类 开头的 Version 部分是 HTTP 协议的版本号&#xff0c;通常是HTTP/1.1&#xff0c;用处不是很大。后面的 Reason 部分是原因短语&#xff0c;是状态码的简短文字描述&#xff…

基于@ControllerAdvice的全局异常处理

进入Controller前的异常 和 Service 层异常 ControllerAdvice: 范围&#xff1a; 全局&#xff0c;适用于所有控制器。 目的&#xff1a; 用于全局配置异常处理和提供全局模型属性。 方法&#xff1a; 在带有 ControllerAdvice 注解的类中的被注解方法将适用于整个应用程序的所…

【论文阅读笔记】Explicit Visual Prompting for Low-Level Structure Segmentations

1.介绍 Explicit Visual Prompting for Low-Level Structure Segmentations 低级结构分割的显式视觉提示 2023年发表在IEEE CVPR Paper Code 2.摘要 检测图像中低级结构&#xff08;低层特征&#xff09;一般包括分割操纵部分、识别失焦像素、分离阴影区域和检测隐藏对象。虽…

软件设计师软考题目解析下午题01

想说的话&#xff1a;要准备软考了。0.0&#xff0c;其实我是不想考的&#xff0c;但是吧&#xff0c;由于本人已经学完所有知识了&#xff0c;只是被学校的课程给锁在那里了&#xff0c;不然早找工作去了。寻思着反正也无聊&#xff0c;就考个证玩玩。 本人github地址&#xf…

C++基于多设计模式下的同步异步日志系统day1

C基于多设计模式下的同步&异步日志系统day1 &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&am…

sqlserver保存微信Emoji表情

首先将数据库字段&#xff0c;设置类型为 nvarchar(200)一个emoji表情&#xff0c;占4字节就可以了&#xff0c;web前端展示不用改任何东西&#xff0c;直接提交数据保存&#xff1b;回显也会没有问题&#xff0c;C#代码不用做任何处理&#xff1b; 不哭不闹要睡觉&#x1f31…

Python:运算符、内置函数和序列基本用法

一、学习目标 1&#xff0e;熟练使用Python运算符。 2&#xff0e;熟练使用Python内置函数。 3&#xff0e;掌握输入、输出函数的使用方法。 4&#xff0e;了解列表、元组、字典、集合的概念和基本用法。 二、相关练习 1&#xff0e;输入一个自然数250&#xff0c;输出其…

王腾飞出席整体系统智能节电设备在工矿企业中的应用

演讲嘉宾&#xff1a;王腾飞 中科兆和电力技术&#xff08;山东&#xff09;有限公司综合能源管理事业部--部长 演讲题目&#xff1a;整体系统智能节电设备在工矿企业中的应用 会议简介 “十四五”规划中提出&#xff0c;提高工业、能源领城智能化与信息化融合&#xff0c;明…

【IC前端虚拟项目】inst_buffer子模块DS与RTL编码

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 需要说明一下的是,在我所提供的文档体系里,并没有模块的DS文档哈,因为实际项目里我也不怎么写DS毕竟不是每个公司都和HISI一样对文档要求这么严格的。不过作为一个培训的虚拟项目,还是建议在时间充裕…

基于JAVA,SpringBoot,Vue,UniAPP智能停车场小程序管理系统设计

摘要 本设计旨在开发一款基于Java, SpringBoot, Vue和UniApp的智能停车场小程序&#xff0c;以实现现代化、智能化的停车管理解决方案。通过整合后端Java SpringBoot框架和前端Vue与UniApp技术&#xff0c;该小程序能够为用户提供一个简洁、高效且用户友好的交互界面&#xff…