前言

记录一次开发中遇到的关于 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. 如何自动申请免费的HTTPS证书?

    在购买域名的时候我相信很多人都遇到了对于证书的问题,之前我也是使用阿里云的免费一年的证书,那时候感觉还好,一年更换一次,但是近期阿里云对于证书的过期时间直接砍到了三个月!让我难以接受,所以我在想吧他直 ...

  2. 【中秋国庆不断更】OpenHarmony组件内状态变量使用:@State装饰器

    [中秋国庆不断更]OpenHarmony组件内状态变量使用:@State装饰器 @State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来.当状态改变时,UI会发生对 ...

  3. 微服务集成Spring Cloud Zipkin实现链路追踪并集成Dubbo

    1.什么是ZipKin Zipkin 是一个根据 Google 发表的论文" Dapper" 进行开源实现的分布式跟踪系统. Dapper是Google 公司内部的分布式追踪系统, ...

  4. Seaborn风格设置

    官方网站:seaborn: statistical data visualization - seaborn 0.11.2 documentation (pydata.org) Seaborn是基于m ...

  5. Mac系统,Qt工程转xcode工程,打包pkg

    序言: 程序使用Qt开发,程序主要功能是调用摄像头.需要打包成pkg给到用户安装,打包用到的是xcode. 实际操作: 一.Qt工程转xcode工程 // 打开终端,cd到项目根目录(CamScan. ...

  6. Hi3861编译烧录更快捷

     原文链接:https://mp.weixin.qq.com/s/TApbA6VUYUVWrGGaDyodbA,点击链接查看更多技术内容: HUAWEI DevEco Device Tool是华为面向 ...

  7. ArcMap分别求取矢量要素各区域的面积

      本文介绍基于ArcMap软件,自动批量计算矢量图层中各个要素的面积的方法.   一次,遇到一个问题,需要分别计算ArcMap软件中一个图层的所有面要素的面积.如图,这个图层中包括多个省级行政区矢量 ...

  8. ABP -Vnext框架一步一步入门落地教程——ABP Vnext框架代码安装和启动(一)

    兄弟们,人生需要指引,而复制是成功最快的方式,让我们开始行动吧 --codesoft 教程介绍 ABP-Vnext框架我们之前摸了无数次,好象初恋的女孩,一直在靠近,一直在努力,一直不敢盯着她的眼睛说 ...

  9. MD5前端vue加密

    Vue 前端md5加密用户注册时将加密后的密码发送给后端存储当登陆的时候,再将加密后的密码和数据库中加密的密码相匹配.npm: https://www.npmjs.com/package/crypto ...

  10. 力扣566(java)-重塑矩阵(简单)

    题目: 在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据. 给你一个由二维数组 mat 表示的  ...