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

相关推荐
利刃大大19 分钟前
【SpringBoot】SpringMVC && 请求注解详解 && 响应注解详解 && Lombok
java·spring boot·后端
她说..29 分钟前
Spring AOP场景4——事务管理(源码分析)
java·数据库·spring boot·后端·sql·spring·springboot
李慕婉学姐1 小时前
Springboot面向电商的仓库管理系统05uc4267(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
pengkai火火火1 小时前
基于springmvc拓展机制的高性能日志审计框架的设计与实现
spring boot·安全·微服务·架构
qq_12498707532 小时前
基于springboot框架的小型饮料销售管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·spring·毕业设计
Jaising6662 小时前
Spring 错误使用事务导致数据可见性问题分析
数据库·spring boot
NMBG222 小时前
外卖综合项目
java·前端·spring boot
小徐Chao努力2 小时前
Spring AI Alibaba A2A 使用指南
java·人工智能·spring boot·spring·spring cloud·agent·a2a
苹果醋33 小时前
Java设计模式实战:从面向对象原则到架构设计的最佳实践
java·运维·spring boot·mysql·nginx
她说..3 小时前
手机验证码功能实现(附带源码)
java·开发语言·spring boot·spring·java-ee·springboot