多租户 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() 参数为指定的线程池Executor executor =  TtlExecutors.getTtlExecutor(threadPoolConfig.threadPool());

纠正后的伪代码如下,

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

具体解决原理不知!!!

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

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

相关文章

阿里云国际云服务器全局流量分析功能详细介绍

进行全局流量分析时&#xff0c;内网DNS解析会作为一个整体模块&#xff0c;其他模块的边缘虚框颜色会置灰&#xff0c;示意作为一个整体进行全局分析&#xff0c;左侧Region可以展开/汇总&#xff0c;也可以单独选中某个Region模块进行分析&#xff08;这时其他Region的流量线…

【Java面试题】Redis的用途

以下是一些常见的用途 1.缓存 Redis 可以用作缓存系统&#xff0c;&#xff0c;将频繁访问的数据存储在内存中&#xff0c;从而加快数据访问速度&#xff0c;减少对数据库的访问压力。 2.消息队列 Redis 支持发布/订阅模式和列表数据结构&#xff0c;可以用作消息队列系统的…

道可云元宇宙每日资讯|厦门首个元宇宙办税大厅启用

道可云元宇宙每日简报&#xff08;2024年3月1日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 中国军号元宇宙发布会即将举行 近日&#xff0c;解放军新闻传播中心中国军号即将正式上线。中国军号元宇宙发布会也将在“云端”与您见面。全方位展现解放军新闻传播…

加密与安全_探索签名算法

文章目录 概述应用常用数字签名算法CodeDSA签名ECDSA签名小结 概述 在非对称加密中&#xff0c;使用私钥加密、公钥解密确实是可行的&#xff0c;而且有着特定的应用场景&#xff0c;即数字签名。 数字签名的主要目的是确保消息的完整性、真实性和不可否认性。通过使用私钥加…

云服务器购买教程

在购买云服务器之前&#xff0c;建议仔细评估自身需求和预算&#xff0c;并与多个云服务提供商进行比较&#xff0c;以确保选择到最适合的解决方案。购买云服务器的具体步骤可能因所选云服务提供商而异。以下以实际操作的方式介绍如何购买一款云服务器。 云服务器购买常见问题…

【数仓】zookeeper软件安装及集群配置

相关文章 【数仓】基本概念、知识普及、核心技术【数仓】数据分层概念以及相关逻辑【数仓】Hadoop软件安装及使用&#xff08;集群配置&#xff09;【数仓】Hadoop集群配置常用参数说明 一、环境准备 准备3台虚拟机 Hadoop131&#xff1a;192.168.56.131Hadoop132&#xff…

【Spring连载】使用Spring Data访问 MongoDB----对象映射之基于类型的转换器

【Spring连载】使用Spring Data访问 MongoDB----对象映射之基于类型的转换器 一、自定义转换二、转换器消歧(Disambiguation)三、基于类型的转换器3.1 写转换3.2 读转换3.3 注册转换器 一、自定义转换 下面的Spring Converter实现示例将String对象转换为自定义Email值对象: R…

蓝桥杯_定时器的综合应用实例

一 工程 代码 在单片机训练平台上&#xff0c;利用定时器T0&#xff0c;数码管模块和2个独立按键&#xff08;J5的2&#xff0c;3短接&#xff09;&#xff0c;设计一个秒表&#xff0c;具有清零&#xff0c;暂停&#xff0c;启动功能。 显示模式&#xff1a;分-秒-0.05秒&…

Linux进程——信号详解(上)

文章目录 信号入门生活角度的信号技术应用角度的信号用kill -l命令可以察看系统定义的信号列表信号处理常见方式概述 产生信号通过键盘进行信号的产生&#xff0c;ctrlc向前台发送2号信号通过系统调用异常软件条件 信号入门 生活角度的信号 你在网上买了很多件商品&#xff0…

前端面试练习24.3.2-3.3

HTMLCSS部分 一.说一说HTML的语义化 在我看来&#xff0c;它的语义化其实是为了便于机器来看的&#xff0c;当然&#xff0c;程序员在使用语义化标签时也可以使得代码更加易读&#xff0c;对于用户来说&#xff0c;这样有利于构建良好的网页结构&#xff0c;可以在优化用户体…

vue3项目中如何一个vue组件中的一个div里面的图片铺满整个屏幕样式如何设置

在Vue 3项目中&#xff0c;要使一个div内的图片铺满整个屏幕&#xff0c;你需要确保几个关键点&#xff1a;div元素和图片元素的样式设置正确&#xff0c;以及确保它们能够覆盖整个视口&#xff08;viewport&#xff09;。以下是一个简单的步骤和代码示例&#xff0c;帮助你实现…

代码随想录算法训练营第四八天 | 买股票

目录 只买卖一次可买卖多次 LeetCode 121. 买卖股票的最佳时机 LeetCode 122. 买卖股票的最佳时机II 只买卖一次 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某…

浏览器输入URL到页面渲染经历了哪些过程?

浏览器输入URL到页面渲染的过程可以分为以下几个步骤&#xff1a; 解析URL&#xff1a;当用户在浏览器的地址栏输入URL后&#xff0c;浏览器会首先解析这个URL&#xff0c;判断其是否合法。查找缓存&#xff1a;浏览器会查看自己的缓存&#xff0c;判断是否有之前访问过的这个U…

论文阅读--Diffusion Models for Reinforcement Learning: A Survey

一、论文概述 本文主要内容是关于在强化学习中应用扩散模型的综述。文章首先介绍了强化学习面临的挑战&#xff0c;以及扩散模型如何解决这些挑战。接着介绍了扩散模型的基础知识和在强化学习中的应用方法。然后讨论了扩散模型在强化学习中的不同角色&#xff0c;并对其在多个…

【JavaSE】实用类——String、日期等

目录 String类常用方法String类的equals()方法String中equals()源码展示 “”和equals()有什么区别呢&#xff1f; StringBuffer类常用构造方法常用方法代码示例 面试题&#xff1a;String类、StringBuffer类和StringBuilder类的区别&#xff1f;日期类Date类Calendar类代码示例…

leetcode169. 多数元素的四种解法

leetcode169. 多数元素 题目描述 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 1.哈希 class Solution { public:int majority…

【vue3】命令式组件封装,message封装示例;(函数式组件?)

仅做代码示例&#xff1b;当然改进的地方还是不少的&#xff0c;仅作为该类组件封装方式的初步启发&#xff1b; 理想大成肯定是想要像 饿了么 这些组件库一样。 有的人叫这函数式组件&#xff0c;有的人叫这命令式组件&#xff0c;我个人还是偏向于命令式组件的称呼。因为以vu…

Django配置静态文件

Django配置静态文件 目录 Django配置静态文件静态文件配置调用方法 一般我们将html文件都放在默认templates目录下 静态文件放在static目录下 static目录大致分为 js文件夹css文件夹img文件夹plugins文件夹 在浏览器输入url能够看到对应的静态资源&#xff0c;如果看不到说明…

向爬虫而生---Redis 探究篇4<Redis主从复制(2)>

前言: 继续上一篇向爬虫而生---Redis 探究篇4&#xff1c;Redis主从复制(1)&#xff1e;-CSDN博客 正文: 读写操作和一致性保证 主节点和从节点对读写操作的不同处理方式 在Redis主从复制中&#xff0c;主节点和从节点对读写操作有不同的处理方式&#xff1a; 主节点&…

vim文本编辑器 的命令及快捷键

vim文本编辑器常用的命令及快捷键 vim文本编辑器功能命令 命令功能i从光标当前位置进入插入模式a从光标下一位进入插入模式ESC键退出编辑模式dd删除2dd删除两行u撤销上一步操作wq保存并退出0光标移动至文本开头G光标移至文本末尾$光标移动至行尾^光标移动至行首q或q!退出不保…