千家信息网

Spring中使用自定义ThreadLocal存储导致的坑及解决方法是什么

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,本篇文章为大家展示了Spring中使用自定义ThreadLocal存储导致的坑及解决方法是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Spring自定义
千家信息网最后更新 2026年11月02日Spring中使用自定义ThreadLocal存储导致的坑及解决方法是什么

本篇文章为大家展示了Spring中使用自定义ThreadLocal存储导致的坑及解决方法是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

Spring自定义ThreadLocal存储导致的坑

Spring 中有时候我们需要存储一些和 Request 相关联的变量,例如用户的登陆有关信息等,它的生命周期和 Request 相同。

一个容易想到的实现办法是使用ThreadLocal

public class SecurityContextHolder {    private static final ThreadLocal securityContext = new ThreadLocal();    public static void set(SecurityContext context) {        securityContext.set(context);    }    public static SecurityContext get() {        return securityContext.get();    }    public static void clear() {        securityContext.remove();    }}

使用一个自定义的HandlerInterceptor将有关信息注入进去

@Slf4j@Componentpublic class RequestInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws            Exception {        try {            SecurityContextHolder.set(retrieveRequestContext(request));        } catch (Exception ex) {            log.warn("读取请求信息失败", ex);        }        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable            ModelAndView modelAndView) throws Exception {        SecurityContextHolder.clear();}

通过这样,我们就可以在 Controller 中直接使用这个 context,很方便的获取到有关用户的信息

@Slf4j@RestControllerclass Controller {  public Result get() {     long userId = SecurityContextHolder.get().getUserId();     // ...  }}

这个方法也是很多博客中使用的。然而这个方法却存在着一个很隐蔽的坑: HandlerInterceptor 的 postHandle 并不总是会调用。

当Controller中出现Exception

@Slf4j@RestControllerclass Controller {  public Result get() {     long userId = SecurityContextHolder.get().getUserId();     // ...     throw new RuntimeException();  }}

或者在HandlerInterceptor的preHandle中出现Exception

@Slf4j@Componentpublic class RequestInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws            Exception {        try {            SecurityContextHolder.set(retrieveRequestContext(request));        } catch (Exception ex) {            log.warn("读取请求信息失败", ex);        }        // ...        throw new RuntimeException();        //...        return true;    }}

这些情况下, postHandle 并不会调用。这就导致了 ThreadLocal 变量不能被清理。

在平常的 Java 环境中,ThreadLocal 变量随着 Thread 本身的销毁,是可以被销毁掉的。但 Spring 由于采用了线程池的设计,响应请求的线程可能会一直常驻,这就导致了变量一直不能被 GC 回收。更糟糕的是,这个没有被正确回收的变量,由于线程池对线程的复用,可能会串到别的 Request 当中,进而直接导致代码逻辑的错误。

为了解决这个问题,我们可以使用 Spring 自带的 RequestContextHolder ,它背后的原理也是 ThreadLocal,不过它总会被更底层的 Servlet 的 Filter 清理掉,因此不存在泄露的问题。

下面是一个使用RequestContextHolder重写的例子

public class SecurityContextHolder {    private static final String SECURITY_CONTEXT_ATTRIBUTES = "SECURITY_CONTEXT";    public static void setContext(SecurityContext context) {        RequestContextHolder.currentRequestAttributes().setAttribute(                SECURITY_CONTEXT_ATTRIBUTES,                context,                RequestAttributes.SCOPE_REQUEST);    }    public static SecurityContext get() {        return (SecurityContext)RequestContextHolder.currentRequestAttributes()                .getAttribute(SECURITY_CONTEXT_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);    }}

除了使用 RequestContextHolder 还可以使用 Request Scope 的 Bean,或者使用 ThreadLocalTargetSource ,原理上是类似的。

需要时刻注意 ThreadLocal 相当于线程内部的 static 变量,是一个非常容易产生泄露的点,因此使用 ThreadLocal 应该额外小心。

Threadlocal可能会产生内存泄露的问题及原理

刚遇到一个关于threadlocal的内存泄漏问题,刚好总结一下

比较常用的这里先不提,直接提比较重要的部分

为什么会产生内存泄露?

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

set方法里面,先调用到当前线程thread,每个线程里都会有一个threadlocals成员变量,指向对应的ThreadLocalMap ,然后以new出来的引用作为key,和给定的value一块保存起来。

当外部引用解除以后,对应的ThreadLocal对象由于被内部ThreadLocalMap 引用,不会GC,可能会导致内存泄露。

JVM解决的办法

static class Entry extends WeakReference> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal k, Object v) {                super(k);                value = v;            }        }

继承了一个软引用,在系统进行gc的时候就可以回收

但是回收以后,key变成null,value也无法被访问到,还是可能存在内存泄露。 因此一旦不用了,必须对里面的keyvalue对remove掉,否则就会有内存泄露;而且在threadlocal源码里面,在每次get或者set的时候会清楚里面key为value的记录

上述内容就是Spring中使用自定义ThreadLocal存储导致的坑及解决方法是什么,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

变量 线程 内存 方法 信息 存储 问题 原理 有关 内容 办法 技能 时候 用户 知识 平常 清楚 相同 简明 糟糕 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 手磨咖啡 互联网科技工作委员会 疫情对互联网科技公司影响 互联网扶贫科技 金融科技创新监管互联网金融 深圳互联网科技公司 远拓互联网科技 互联网 视角下的科技创新 甘肃拓世互联网科技有限公司 海南购全球互联网科技 互联网科技项目计划书材料类 成都铂石互联网科技做什么的 互联网软件和科技 想开互联网科技公司怎么样 如何创新互联网科技 互联网公司黑科技创始人 南通创西澳互联网科技有限公司 智帮互联网科技公司最近动向 美国五大互联网科技公司市值多少 科技互联网企业在行动 厦门中拓互联网络科技有限公司 狮子座互联网科技 广州紫鲸互联网科技 蓝色互联网科技 博兴的互联网电子科技公司 央行互联网金融科技公司 互联网软件和科技 中国航天科技大学国际工业互联网 互联网5g黑科技有哪些 浙南科技城互联网大厦建设工程
0