记一次ThreadLocal中的用户信息混乱问题

前言

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


一、为什么使用 ThreadLocal

1. ThreadLocal 的好处

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

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

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

2. 注意事项

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

  • 内存泄漏: ThreadLocal 是与线程绑定的,如果线程一直存在,那么对应的变量副本也会一直存在,可能会占用大量的内存空间,如不及时清理 ,可能会导致内存泄漏问题。

  • **上下文切换问题:**由于每个线程都有自己的变量副本,当需要在多个线程之间共享数据时,可能需要进行额外的上下文切换操作,增加了程序的复杂性和开销。

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

3. 如何实现

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

创建用户信息工具类

java 复制代码
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 中即可。然后最后记得清除相关数据以避免内存泄漏。

java 复制代码
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 的过滤器和拦截器

二、问题记录

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

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

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

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

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

相关推荐
杨半仙儿还未成仙儿1 小时前
Spring框架:Spring Core、Spring AOP、Spring MVC、Spring Boot、Spring Cloud等组件的基本原理及使用
spring boot·spring·mvc
攸攸太上5 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
无理 Java9 小时前
【技术详解】SpringMVC框架全面解析:从入门到精通(SpringMVC)
java·后端·spring·面试·mvc·框架·springmvc
gobeyye10 小时前
spring loC&DI 详解
java·spring·rpc
java66666888810 小时前
Java中的对象生命周期管理:从Spring Bean到JVM对象的深度解析
java·jvm·spring
王维诗里的代码i12 小时前
Redis基础二(spring整合redis)
java·数据库·redis·spring
椰椰椰耶13 小时前
【Spring】@RequestMapping、@RestController和Postman
java·后端·spring·mvc
大道归简19 小时前
2.点位管理开发(续)及设计思路——帝可得后台管理系统
java·开发语言·spring boot·spring·前端框架
程序员一点1 天前
Python并发编程(1)——Python并发编程的几种实现方式
python·多线程·并发编程·多进程
PacosonSWJTU1 天前
spring揭秘25-springmvc04-servlet容器与springmvc容器总结
spring·springmvc