前言

记录一次开发中遇到的关于 ThreadLocal 问题,场景是数据库表中的操作人总是无缘无故的被更改,排查了几遍代码才发现是 ThreadLocal 没有及时清理导致的。


一、为什么使用 ThreadLocal

1. ThreadLocal 的好处

一般的项目设计开发中,用户登录后,我们会将用户的信息存到 Session,如果想在其它地方获取用户信息,需要频繁的传递 Session 对象。如果遇到高并发,多人同时登录系统时,可能会出现 Session 混乱,导致获取的用户信息不一致。

而 ThreadLocal 可以将用户信息保存在本地线程变量中,当线程结束后我们在把用户信息清理掉。这样在进行开发时,就可以很方便的从全局获取用户信息,不需要频繁的传递 Session 对象,提升开发效率。

并且 ThreadLocal 是线程隔离的,每个线程都有自己独立的变量副本,不会受到其他线程的影响,可以避免线程安全问题。

2. 注意事项

ThreadLocal 也是有缺点的,有两个比较突出的缺点是内存泄漏和上下文切换问题:

  • 内存泄漏:ThreadLocal 是与线程绑定的,如果线程一直存在,那么对应的变量副本也会一直存在,可能会占用大量的内存空间,如不及时清理 ,可能会导致内存泄漏问题。
  • 上下文切换问题:由于每个线程都有自己的变量副本,当需要在多个线程之间共享数据时,可能需要进行额外的上下文切换操作,增加了程序的复杂性和开销。

所以我们在业务逻辑结束时,一定要清理一下 ThreadLocal 的数据

3. 如何实现

不管过滤器还是拦截器,只要是能拦截住请求,获取到用户信息均可,这里简单介绍拦截器的实现方法。

创建用户信息工具类

public class CurrentUserUtil {

    /**
* 初始化用户对象的ThreadLocal
*/
private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); /**
* 添加当前登录用户方法
*/
public static void addCurrentUser(User user){
userThreadLocal.set(user);
} public static User getCurrentUser(){
return userThreadLocal.get();
} /**
* 防止内存泄漏
*/
public static void remove(){
userThreadLocal.remove();
} }

拦截器中调用,在preHandle()方法中根据业务需求将用户的登录信息存放之 ThreadLocal 中即可。然后最后记得清除相关数据以避免内存泄漏。

public class UnifyInterceptor implements HandlerInterceptor {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = new User();
user.setWorkNo("123456");
CurrentUserUtil.addCurrentUser(user);
return true;
} /**
* 避免内存泄露
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserInfoThreadHolder.remove();
}
}

这里关于拦截器的详细使用不多做赘述,详细介绍见[Spring 的过滤器和拦截器](https://www.cnblogs.com/fuxing/p/18188764)。

二、问题记录

测试同学在项目合同签约完以后,发现签约完成后数字资产的 Owner 被改变了。然后去排查代码,开始并没有发现代码有问题,于是去排查 ThreadLocal 中的用户信息,发现是操作人已经不一致。所以在执行后续操作时数据库中的操作人就被修改了。



按理说,在设置用户信息之前第一次获取的值始终应该是 null,但是请求线程被 Tomcat 回收后,不一定会立即销毁,如果不在请求结束后主动 remove 线程中的 ThreadLocal 信息,可能会影响后续逻辑,拿到脏数据。



这里我还犯了一个小错误,我知道 ThreadLocal 会产生内存泄漏,需要进行清除操作,但是我把它放在方法执行前了,也就是说每次请求会现进行清除操作,正常流程是不会出错的。



但是当我们通过 MQ 消息队列接受消息时,按理说此时的 ThreadLocal 里的用户信息应该为 null,但由于没有在方法执行结束前及时清理 ThreadLocal,导致了用户信息出现了不一致的情况。后续我将问题 clear 方法放在了结束时执行,并在消息监听的方法里也进行了清理保证不会被其他用户信息所干扰。



所以,不论使用 ThreadLocal 存什么数据,请务必记得,在业务逻辑结束之前清理 ThreadLocal 中的数据。

记一次ThreadLocal中的用户信息混乱问题的更多相关文章

  1. 获得session中的用户信息

    由于每个系统都有往session中放入用户信息以及把用户信息取出来的模块,而且在session中取出用户信息的地方非常之多,所以有必要把session中对用户的操作封装成为一个工具类,以便在以后的使用 ...

  2. 查询oracle中所有用户信息 禁用用户

    ----查询oracle中所有用户信息 ----1.查询数据库中的表空间名称 ----1)查询所有表空间 select tablespace_name from dba_tablespaces; se ...

  3. 小程序app.onLaunch中获取用户信息,index.onLoad初次载入时取不到值的问题

    问题描述: //app.js App({ globalData:{ nickname:'' }, onLaunch: function () { let that=this; //假设已经授权成功 w ...

  4. chmod a+w . 权限控制 su、sudo 修改文件所有者和文件所在组 添加用户到sudoer列表中 当前用户信息

    对当前目录对所有用户开放读写权限 chmod a+r . $ sudo chmod -R a+w /usr/lib/python2.7 所有用户添加文件的写权限 [linux]su.sudo.sudo ...

  5. 微信小程序~App.js中获取用户信息

    (1)代码:主要介绍下获取用户信息部分 onLaunch: function () { // 展示本地存储能力 var logs = wx.getStorageSync('logs') || [] l ...

  6. 【转】如何打开注册表编辑器中存储用户信息的SAM文件?

    sam文件怎么打开 (Security Accounts Manager安全帐户管理器)负责SAM数据库的控制和维护.SAM数据库位于注册表HKLM\SAM\SAM下,受到ACL保护,可以使用rege ...

  7. 如何查询Oracle中所有用户信息

    1.查看所有用户: select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角色系统权限( ...

  8. 查询oracle中所有用户信息

    1.查看所有用户:select * from dba_users;   select * from all_users;   select * from user_users; 2.查看用户或角色系统 ...

  9. oracle中查询用户信息

    1.查看所有用户: select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角色系统权限( ...

  10. spring security中当前用户信息

    1:如果在jsp页面中获取可以使用spring security的标签库 在页面中引入标签   1 <%@ taglib prefix="sec" uri="htt ...

随机推荐

  1. #双指针#洛谷 7521 [省选联考 2021 B 卷] 取模

    题目传送门 分析 将 \(a\) 排序后从大到小枚举 \(a_k\),注意枚举的时候重复的只考虑一次,那么可以将其它数按照模 \(a_k\) 后排序, 答案只可能来自最大值与次大值之和取模或者之和最接 ...

  2. 2023 LGR 非专业级别软件能力认证第一轮(初赛)S组

    计算器.背包.代码都不能带进考场 禁赛三年并全国通报 B选项符合while语句 弱类型编程语言指的是可以进行类型转换,可以参与各种类型变量的运算 \[3\times 60(秒)\times 44.1\ ...

  3. #FFT#P4173 残缺的字符串

    题目 有一个长度为 \(m\) 的字符串 \(A\) 和 一个长度为 \(n\) 的字符串 \(B\), 它们有若干位置为通配符,现在问 \(B\) 的每一个后缀是否存在前缀为 \(A\). 分析 考 ...

  4. OpenHarmony社区运营报告(2023年4月)

      本月快讯 • 2023年4月9日,OpenAtom OpenHarmony(以下简称"OpenHarmony")3.2 Release新版本发布.相比一年前的OpenHarmo ...

  5. WPF/MVVM模式入门教程(二):实现INotifyPropertyChanged接口

    引用:https://www.cnblogs.com/flh1/p/12447188.html 1.创建NotifyPropertyChanged类 我们在common文件夹下创建一个名为Notify ...

  6. Qt调用摄像头一,基础版

    本示例,为纯Qt调用摄像头,功能比较简单,打开摄像头,设置参数,拍照 涉及到的功能有: 获取摄像头列表 获取摄像头分辨率 获取摄像头帧率 获取摄像头支持的视频模式 设置摄像头参数 拍照 此版本的缺点是 ...

  7. k8s之存储卷OpenEBS

    一.OpenEBS简介 OpenEBS 是一种开源云原生存储解决方案,托管于 CNCF 基金会,目前该项目处于沙箱阶段. OpenEBS能够将Kubernetes工作节点上可用的住何存储转换为术卷或分 ...

  8. 在python中实现二叉树

    二叉树设计 定义节点类 class Node: # 修改初始化方法 def init(self,value): self.value = value # 节点值 self.left = None # ...

  9. 使用input标签的时候报错,提示Form elements must have labels: Element has no title attribute Element has no placeholder attribute

    使用input标签的时候报错,提示Form elements must have labels: Element has no title attribute Element has no place ...

  10. 阿里云服务网格 ASM 正式发布商业化版本

    ​简介:为了更好地满足企业日益加深的大规模使用服务网格产品.服务多语言互通.服务精细治理等需求,2022 年 4 月 1 日起,阿里云服务网格产品 ASM 正式发布商业化版本,为企业在生产环境下大规模 ...