一、使用场景

对于token的解析,controller层写了一遍,拦截器也写了一遍;
优化:只在拦截器中解析token,controller中使用拦截器中解析的结果。

二、ThreadLocal概念

1. 图的含义
这张图画出了一个 ThreadLocal
对象(中间的红色方块 ThreadLocal tl
),它被两个不同的线程(左边蓝色、右边绿色)使用:
-
左边线程(蓝色)
javatl.set("蒜炎"); tl.get();
在这个线程中,调用
set("蒜炎")
,以后在同一线程里get()
都能取到"蒜炎"
。 -
右边线程(绿色)
javatl.set("药尘"); tl.get();
在另一个线程中,调用
set("药尘")
,这个线程自己能拿到"药尘"
,但左边线程拿不到。
中间的 ThreadLocal
对象并没有直接保存数据 ,它只是一个"索引键",每个线程内部各自维护一份 ThreadLocalMap (相当于一个 Map<ThreadLocal, Object>
)。
所以 每个线程自己有独立的一份数据,互不干扰,线程安全。
2. 工作原理
-
每个线程都有一个隐藏的 Map
javaThread (当前线程) └── ThreadLocalMap └── key = tl (ThreadLocal 实例) └── value = "蒜炎" // 或 "药尘"
-
调用
tl.set(value)
-
就是
当前线程.threadLocals.put(tl, value)
,把值和tl
这个键关联。 -
每个线程只在自己的
Map
中保存,不会写到别的线程。
-
-
调用
tl.get()
- 就是从
当前线程.threadLocals
查找tl
对应的值。
- 就是从
-
调用
tl.remove()
- 只清除 当前线程 里这个
tl
的值,不会影响其他线程。
- 只清除 当前线程 里这个
【注意】:
ThreadLocal
在同一个线程里只有一个槽位每次
set(value)
都是 覆盖之前的值(旧值会被丢弃,只保留最新的)。可以理解为:
javaThreadLocal<String> local = new ThreadLocal<>(); local.set("123"); // map.put(local, "123") local.set("abc"); // map.put(local, "abc") ------ 把"123"覆盖掉 local.remove(); // map.remove(local)
调用顺序是:
set("123")
→ 存"123"
set("abc")
→ 覆盖"123"
,旧值会被 GC
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方法:
