ThreadLocal 是 Java 里非常优雅但又容易被忽视的一个工具,用来给每个线程存独立的数据副本。
你可以把它当成:
✔ 线程内部自己的小仓库
✔ 线程级别的全局变量容器
🔵 1. 用一句大白话解释
ThreadLocal 让每个线程拥有自己的一份数据,互不干扰。
就像 每个学生一张试卷 ——
你在自己卷子上写答案,并不会影响别人卷子。
🔵 2. 它用来解决什么问题?
在多线程环境,如果多个线程共享一个变量,会出现:
❌ 数据错乱
❌ 并发覆盖
❌ 取不到正确线程的上下文
ThreadLocal 的作用就是:
✔ 每个线程存自己的值
✔ 互不影响
✔ 不需要加锁也能绝对线程安全
🔵 3. 用 ThreadLocal 做什么?
最常见用途(特别是你做后台项目时一定用到):
✔ 存储登录用户信息(Spring 拦截器里超常用)
1 | public class UserHolder { |
这样 Controller、Service 随时可以拿到当前用户:
1 | UserDTO user = UserHolder.get(); |
✔ 事务ID / trace ID / 日志跟踪
1 | ThreadLocal<String> traceId = new ThreadLocal<>(); |
✔ 日期解析器(避免线程不安全的 SimpleDateFormat)
1 | private static final ThreadLocal<SimpleDateFormat> sdf = |
🔵 4. ThreadLocal 的核心 API
| 方法 | 作用 |
|---|---|
set() |
设置当前线程的值 |
get() |
获取该线程的值 |
remove() |
清除存储(非常重要 防泄露) |
推荐写法:
1 | private static ThreadLocal<User> tl = new ThreadLocal<>(); |
🔵 5. 内部结构怎么理解?(简单但重要)
ThreadLocal 自己不存值
✔ 真正的数据存在线程(Thread)的 ThreadLocalMap 里
✔ 一个线程有一个 map,key 是 ThreadLocal 对象
理解结构:
1 | 线程对象 Thread |
所以每个线程独立的一份数据,互不影响。
🔥 6. 容易犯的错误:不 remove
ThreadLocal 的 entry key 是弱引用,value 是强引用,
➡ 若你不 remove:
✔ key 会被 GC 回收
✔ value 存在线程里
✔ 导致 value 永远得不到清理
✔ 就叫 ThreadLocal 内存泄漏
正确写法
✔ 在 finally 或拦截器 afterCompletion() 中 remove
1 | try{ |
在 Spring 拦截器中:
1 |
|
⚠ 这是你项目必须做的!
🔵 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 是最常见场景