ThreadLocal

ThreadLocal 是 Java 里非常优雅但又容易被忽视的一个工具,用来给每个线程存独立的数据副本

你可以把它当成:

线程内部自己的小仓库
线程级别的全局变量容器



🔵 1. 用一句大白话解释

ThreadLocal 让每个线程拥有自己的一份数据,互不干扰。

就像 每个学生一张试卷 ——
你在自己卷子上写答案,并不会影响别人卷子。



🔵 2. 它用来解决什么问题?

在多线程环境,如果多个线程共享一个变量,会出现:

❌ 数据错乱
❌ 并发覆盖
❌ 取不到正确线程的上下文

ThreadLocal 的作用就是:

✔ 每个线程存自己的值
✔ 互不影响
✔ 不需要加锁也能绝对线程安全



🔵 3. 用 ThreadLocal 做什么?

最常见用途(特别是你做后台项目时一定用到):

✔ 存储登录用户信息(Spring 拦截器里超常用)

1
2
3
4
5
6
7
8
9
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

public static void set(UserDTO user){ tl.set(user); }

public static UserDTO get(){ return tl.get(); }

public static void remove(){ tl.remove(); }
}

这样 Controller、Service 随时可以拿到当前用户:

1
UserDTO user = UserHolder.get();


✔ 事务ID / trace ID / 日志跟踪

1
2
ThreadLocal<String> traceId = new ThreadLocal<>();
traceId.set("req-12345");


✔ 日期解析器(避免线程不安全的 SimpleDateFormat)

1
2
private static final ThreadLocal<SimpleDateFormat> sdf =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));


🔵 4. ThreadLocal 的核心 API

方法 作用
set() 设置当前线程的值
get() 获取该线程的值
remove() 清除存储(非常重要 防泄露)

推荐写法:

1
2
3
4
5
6
7
private static ThreadLocal<User> tl = new ThreadLocal<>();

tl.set(user); // 绑定
...
tl.get(); // 获取
...
tl.remove(); // 清除 避免内存泄漏


🔵 5. 内部结构怎么理解?(简单但重要)

ThreadLocal 自己不存值
✔ 真正的数据存在线程(Thread)的 ThreadLocalMap 里
✔ 一个线程有一个 map,key 是 ThreadLocal 对象

理解结构:

1
2
3
4
线程对象 Thread
└── ThreadLocalMap
├── key = ThreadLocal 对象A → value=数据A
└── key = ThreadLocal 对象B → value=数据B

所以每个线程独立的一份数据,互不影响。



🔥 6. 容易犯的错误:不 remove

ThreadLocal 的 entry key 是弱引用,value 是强引用,

➡ 若你不 remove:

✔ key 会被 GC 回收
✔ value 存在线程里
✔ 导致 value 永远得不到清理
✔ 就叫 ThreadLocal 内存泄漏

正确写法

✔ 在 finally 或拦截器 afterCompletion() 中 remove

1
2
3
4
5
6
try{
tl.set(user);
...
}finally {
tl.remove();
}

在 Spring 拦截器中:

1
2
3
4
@Override
public void afterCompletion(...) {
UserHolder.remove(); // 释放线程内存
}

⚠ 这是你项目必须做的!



🔵 7. ThreadLocal vs 全局变量 vs 参数传递

方式 是否线程安全 是否每线程独立 侵入性
全局变量
方法参数传递 高(每层都要传)
ThreadLocal 低(无需改方法签名)

➡ 所以 ThreadLocal 特别适合 跨层传递用户上下文 / traceId



🔵 8. 用 ThreadLocal 的最佳模式

① 封装工具类 UserContext / UserHolder

② 拦截器里 set()

③ Controller / Service 里随取随用

④ 拦截器 afterCompletion() 里 remove()

✔ 你的 SpringCloud / Web 项目登录态就是这个用法



🔥 9. 和 AOP / Filter 搭配效果更好

特别适合:

✔ JWT 解析
✔ 登录鉴权
✔ 数据权限隔离
✔ 链路追踪 traceId



🟣 总结

👉 ThreadLocal = 每个线程的私有存储容器
👉 使用时一定记得 remove 防止内存泄漏
👉 用于存登录用户、上下文、traceId 是最常见场景