记一次ThreadLocal中的用户信息混乱问题
前言
记录一次开发中遇到的关于 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中的用户信息混乱问题的更多相关文章
- 获得session中的用户信息
由于每个系统都有往session中放入用户信息以及把用户信息取出来的模块,而且在session中取出用户信息的地方非常之多,所以有必要把session中对用户的操作封装成为一个工具类,以便在以后的使用 ...
- 查询oracle中所有用户信息 禁用用户
----查询oracle中所有用户信息 ----1.查询数据库中的表空间名称 ----1)查询所有表空间 select tablespace_name from dba_tablespaces; se ...
- 小程序app.onLaunch中获取用户信息,index.onLoad初次载入时取不到值的问题
问题描述: //app.js App({ globalData:{ nickname:'' }, onLaunch: function () { let that=this; //假设已经授权成功 w ...
- chmod a+w . 权限控制 su、sudo 修改文件所有者和文件所在组 添加用户到sudoer列表中 当前用户信息
对当前目录对所有用户开放读写权限 chmod a+r . $ sudo chmod -R a+w /usr/lib/python2.7 所有用户添加文件的写权限 [linux]su.sudo.sudo ...
- 微信小程序~App.js中获取用户信息
(1)代码:主要介绍下获取用户信息部分 onLaunch: function () { // 展示本地存储能力 var logs = wx.getStorageSync('logs') || [] l ...
- 【转】如何打开注册表编辑器中存储用户信息的SAM文件?
sam文件怎么打开 (Security Accounts Manager安全帐户管理器)负责SAM数据库的控制和维护.SAM数据库位于注册表HKLM\SAM\SAM下,受到ACL保护,可以使用rege ...
- 如何查询Oracle中所有用户信息
1.查看所有用户: select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角色系统权限( ...
- 查询oracle中所有用户信息
1.查看所有用户:select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角色系统 ...
- oracle中查询用户信息
1.查看所有用户: select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角色系统权限( ...
- spring security中当前用户信息
1:如果在jsp页面中获取可以使用spring security的标签库 在页面中引入标签 1 <%@ taglib prefix="sec" uri="htt ...
随机推荐
- 6 HTML图片标签
6 图片标签 在HTML中,图像由标签定义的,它可以用来加载图片到html网页中显示.网页开发过程中,有三种图片格式被广泛应用到web里,分别是 jpg.png.gif. img标签的属性: /* s ...
- HTTPS加密套件的笔记
本文于2016年5月份完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. 按照如下配置(适用于Tomcat 7.0.x),为Tomcat启用了 ...
- 区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新
目录 简介 什么是区块链 区块链不是什么 区块链的基础:密码学 区块链的基础:分布式系统和共识机制 超级账本Hyperledger 以太坊 Libra 比特币 总结 简介 区块链是一种防篡改的共享数字 ...
- OpenHarmony 3.2 Beta多媒体系列——视频录制
一.简介 媒体子系统为开发者提供了媒体相关的很多功能,本文针对其中的视频录制功能做个详细的介绍.首先,我将通过媒体子系统提供的视频录制Test代码作为切入点,给大家梳理一下整个录制的流程. 二.目录 ...
- CircleIndicator组件,使指示器风格更加多样化
UI界面是应用程序可视化必不可少的部分.设计精致的UI界面可以使得整个可视化应用程序给用户留下深刻的印象,是改善用户界面体验最直接的方式. ArkUI开发框架为开发者提供了丰富的UI原生组件,如Nav ...
- Go 语言函数、参数和返回值详解
函数是一组语句,可以在程序中重复使用.函数不会在页面加载时自动执行.函数将通过调用函数来执行. 创建函数 要创建(通常称为声明)一个函数,请执行以下操作: 使用 func 关键字. 指定函数的名称,后 ...
- HMS Core 3D Engine助您实现逼真3D渲染效果,构筑大型3D数字世界
HMS Core 3D Engine是一款高性能.高画质.高可靠的实时3D引擎,旨在帮助开发者制作高品质的3D应用.3D Engine将为您提供可编程渲染管线,多维粒子系统,3D角色与动画,超大地形地 ...
- mupdf实用操作demo,C++操作PDF文件
前文: 最近有个项目,需要读写PDF,本来想着挺简单的,读写PDF有那么多的库可以使用,唰唰的就完成了. 忘记了我写C++的,还是在国产系统上开发的. 所以一般的东西还不好使,因为项目需要在多个架构的 ...
- ContOS7搭建RAID-0磁盘阵列
RAID-0条带数据: 优点:2块硬盘同时在写数据,而且各写各的不影响,速度较快:性能提升2倍(理论): 缺点:服务器硬盘特别容易损坏,一点损坏一个,其余不能用:没有容错性:服务器用的不多,都是配合使 ...
- 混合app 解决常见bug弹出键盘返回出现闪屏
前言 在我们开发混合app中,我们会发现一个问题,那就是比如我们正在输入信息的时候,然后我们按导航的返回键返回,因为切页面和回收键盘是同时的,给人一种闪屏的感觉,那么怎么解决呢? 方案 以ionic为 ...