前言

记录一次开发中遇到的关于 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. js实现多列排序-存在问题

    js实现多列排序 根据业务逻辑调整 sortData 的数据. 排序的规则是按照第一列排序,第一列相同按照第二列排序,依次类推 // 要排序的数据 const array = [{ name: '甲' ...

  2. #威佐夫博弈#洛谷 2252 [SHOI2002]取石子游戏

    题目 有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子. 游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子: 二是可以在两堆中同时取走相同数量的石子.最后把石子全部取完 ...

  3. 【版本发布公告】HMS Core6.5.0来啦

    新 能 力 3D Engine 3D Engine提供高性能.高画质.高扩展性的实时3D引擎,并提供便捷高效的可视化开发工具.开发者可基于华为的3D Studio开发工具,通过图形和渲染.动画.UI等 ...

  4. ArkUI开发趣味体验,快来抽取限量HarmonyOS专属头像!

    本次ArkUI开发趣味体验活动,将手把手教大家如何在IDE里实操一个ArkUI程序,通过补充缺失代码,成功运行程序开启抽奖功能,抽取个人专属头像,做HarmonyOS第一批数字藏品家! 同时本期提供的 ...

  5. Batch Normalization 和 DropOut

    Batch-Normalization https://www.cnblogs.com/guoyaohua/p/8724433.html 有几点需要注意: \(x^{(k)}\)指的是t层的输入. 也 ...

  6. Pytorch DistributedDataParallel(DDP)教程二:快速入门实践篇

    一.简要回顾DDP 在上一篇文章中,简单介绍了Pytorch分布式训练的一些基础原理和基本概念.简要回顾如下: 1,DDP采用Ring-All-Reduce架构,其核心思想为:所有的GPU设备安排在一 ...

  7. 实时数仓构建:Flink+OLAP查询的一些实践与思考

    今天是一篇架构分享内容. 1.概述 以Flink为主的计算引擎配合OLAP查询分析引擎组合进而构建实时数仓,其技术方案的选择是我们在技术选型过程中最常见的问题之一.也是很多公司和业务支持过程中会实实在 ...

  8. 百度unit闲聊机器人

    import json import random import requests # client_id 为官网获取的AK, client_secret 为官网获取的SK client_id = & ...

  9. 【ESP32 IDF】用RMT控制 WS2812 彩色灯带

    在上一篇中,老周用 .NET Nano Framework 给大伙伴们演示了 WS2812 灯带的控制,包括用 SPI 和 红外RMT 的方式.利用 RMT 是一个很机灵的方案,不过,可能很多大伙伴对 ...

  10. Maxcompute-UNION数据类型对齐的方法

    简介: 怎么对齐两段union脚本的数据类型 第1章      问题概述 1.1     UNION中隐式类型转换问题 近期参与的一个私有云项目要升级,因为maxcompute要升级到更新的版本,对之 ...