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

相关推荐
摇滚侠3 小时前
【IT老齐456】Spring Boot优雅开发多线程应用,笔记01
spring boot·redis·笔记
还是鼠鼠3 小时前
《黑马商城》微服务保护-详细介绍【简单易懂注释版】
java·spring boot·spring·spring cloud·sentinel·maven
Terio_my4 小时前
Spring Boot 热部署配置
java·spring boot·后端
苹果醋35 小时前
数据结构其一 线性表
java·运维·spring boot·mysql·nginx
EnCi Zheng6 小时前
JJWT 依赖包完全指南-从入门到精通
java·spring boot
这周也會开心6 小时前
Tomcat本地部署SpringBoot项目
java·spring boot·tomcat
星秀日8 小时前
框架--SpringBoot
java·spring boot·后端
老朋友此林14 小时前
MongoDB GEO 项目场景 ms-scope 实战
java·数据库·spring boot·mongodb
小蒜学长16 小时前
springboot二手儿童绘本交易系统设计与实现(代码+数据库+LW)
java·开发语言·spring boot·后端