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方法:

相关推荐
JavaTree20171 分钟前
【Spring Boot】Spring Boot解决循环依赖
java·spring boot·后端
凤山老林2 小时前
还在用JDK8?JDK8升级JDK11:一次价值千万的升级指南
java·开发语言·jvm·spring boot·后端·jdk
众俗2 小时前
Linux+Docker+SpringBoot 简单部署
linux·spring boot·docker
Java之路行者2 小时前
Spring Boot防重复提交实战:让接口安全提升200%!
spring boot·后端·安全
optimistic_chen5 小时前
【Java EE进阶 --- SpringBoot】统一功能处理(拦截器)
spring boot·后端·java-ee·log4j·拦截器
没有bug.的程序员6 小时前
Spring Boot 常见性能与配置优化
java·spring boot·后端·spring·动态代理
没有bug.的程序员6 小时前
Spring Boot Actuator 监控机制解析
java·前端·spring boot·spring·源码
骇客野人7 小时前
Spring Boot项目快速稳健架构指南
spring boot·后端·架构
小猪绝不放弃.7 小时前
Spring Boot项目的核心依赖
java·spring boot·后端
ss2738 小时前
基于Springboot + vue3实现的药材中药资源共享平台
java·spring boot·后端