SpringBoot14-ThreadLocal讲解

一、使用场景

对于token的解析,controller层写了一遍,拦截器也写了一遍;

优化:只在拦截器中解析token,controller中使用拦截器中解析的结果。

二、ThreadLocal概念


1. 图的含义

这张图画出了一个 ThreadLocal 对象(中间的红色方块 ThreadLocal tl),它被两个不同的线程(左边蓝色、右边绿色)使用:

  • 左边线程(蓝色)

    java 复制代码
    tl.set("蒜炎");
    tl.get();

    在这个线程中,调用 set("蒜炎"),以后在同一线程里 get() 都能取到 "蒜炎"。

  • 右边线程(绿色)

    java 复制代码
    tl.set("药尘");
    tl.get();

    在另一个线程中,调用 set("药尘"),这个线程自己能拿到 "药尘",但左边线程拿不到。

中间的 ThreadLocal 对象并没有直接保存数据 ,它只是一个"索引键",每个线程内部各自维护一份 ThreadLocalMap (相当于一个 Map<ThreadLocal, Object>)。

所以 每个线程自己有独立的一份数据,互不干扰,线程安全。


2. 工作原理

  1. 每个线程都有一个隐藏的 Map

    java 复制代码
    Thread (当前线程)
      └── ThreadLocalMap
            └── key = tl (ThreadLocal 实例)
            └── value = "蒜炎"  // 或 "药尘"
  2. 调用 tl.set(value)

    • 就是 当前线程.threadLocals.put(tl, value),把值和 tl 这个键关联。

    • 每个线程只在自己的 Map 中保存,不会写到别的线程。

  3. 调用 tl.get()

    • 就是从 当前线程.threadLocals 查找 tl 对应的值。
  4. 调用 tl.remove()

    • 只清除 当前线程 里这个 tl 的值,不会影响其他线程。

【注意】:

ThreadLocal 在同一个线程里只有一个槽位

每次 set(value) 都是 覆盖之前的值(旧值会被丢弃,只保留最新的)。

可以理解为:

java 复制代码
ThreadLocal<String> local = new ThreadLocal<>();

local.set("123"); // map.put(local, "123")
local.set("abc"); // map.put(local, "abc") ------ 把"123"覆盖掉
local.remove();   // map.remove(local)

调用顺序是:

  1. set("123") → 存 "123"

  2. set("abc") → 覆盖 "123",旧值会被 GC

  3. remove() → 删除 "abc"

此时 local.get() 就是 null。


3. 为什么线程安全

因为数据是以 当前线程为单位 存储的,不同线程之间根本不共享这份值:

  • 左边线程即使设置 "蒜炎",右边线程 get() 也拿不到;

  • 右边线程改成 "药尘",左边线程也完全不会受到影响。


4. 常见用途

  • 保存用户上下文(如当前登录用户信息、TraceId、租户信息)

  • 事务管理(如数据库连接、Session)

  • 避免方法参数层层传递(在同一线程内共享数据)

注意:如果线程是线程池复用的,一定要在用完后调用 remove(),避免旧数据"污染"后续任务。


5、编写测试类

三、ThreadLocal-避免方法传参层层递进

在 Tomcat 这样的 Web 容器中,每一个请求通常会被分配给一个线程去处理。图示有两个请求:

  • 第一个用户(userId=1)进入后由线程 A 处理;

  • 第二个用户(userId=2)进入后由线程 B 处理。

使用ThreadLocal之后,可以实现请求上下文共享:

  • 这样我们不需要在 Controller -> Service -> Dao 之间层层传递 userId 参数;

  • 每一层直接从 ThreadLocal 中获取即可,代码更简洁。

【注意】:

一定要在请求结束时调用 remove(),防止线程池复用时出现数据污染和内存泄漏。

四、ThreadLocal工具类的编写

java 复制代码
/**
 * ThreadLocal 工具类
 * 用于在线程内保存和获取与当前线程绑定的数据
 */
public class ThreadLocalUtil {

    // 使用泛型支持存储任意类型
    private static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 设置当前线程的数据
     *
     * @param value 任意需要存储的对象
     */
    public static void set(Object value) {
        THREAD_LOCAL.set(value);
    }

    /**
     * 获取当前线程的数据
     *
     * @param <T> 期望返回的类型
     * @return 当前线程绑定的数据
     */
    @SuppressWarnings("unchecked")
    public static <T> T get() {
        return (T) THREAD_LOCAL.get();
    }

    /**
     * 删除当前线程绑定的数据
     * (建议在任务完成后调用,避免内存泄漏)
     */
    public static void remove() {
        THREAD_LOCAL.remove();
    }

    private ThreadLocalUtil() {
        // 工具类不允许实例化
    }
}

清除ThreadLocal变量

在请求完成之后,清除ThreadLocal变量,在拦截器中重写afterCompletion方法:

相关推荐
paopaokaka_luck5 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
SL_staff5 小时前
JVS混合架构实践:能力引擎+动态模型如何支撑企业级可持续交付
spring boot·低代码·全栈
迪飞特科技5 小时前
智慧物业平台实战:Spring Boot 整合小程序实现报修巡检费用全流程
spring boot·后端·小程序
考虑考虑5 小时前
@JsonInclude注解使用
spring boot·后端·spring
凤山老林10 小时前
Spring Boot 集成 Redis Stream 做可靠消息队列:持久化、消费组重组与流式处理实战
spring boot·redis·后端
.Hypocritical.11 小时前
SpringBoot+Vue 前后端分离项目部署到云服务器
服务器·vue.js·spring boot
云和数据.ChenGuang12 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整指南
java·人工智能·spring boot·后端·重构·springai
豆角焖肉12 小时前
SpringBoot 集成腾讯 AI 人脸识别
人工智能·spring boot·后端·人脸识别·腾讯 ai·base64 图片
十年Java程序媛13 小时前
Java String、StringBuilder、StringBuffer 底层原理|字符串拼接性能与线程安全
java·开发语言·spring boot·安全·面试
她的男孩13 小时前
升级 Spring Boot 4 后流程起不来、登录却报缺租户:Forge Admin 补上 applicationTaskExecutor 与旧 Sa-Tok
spring boot·安全·工作流引擎