文章目录
-
- 核心解决的两个大痛点
-
- [1. 线程安全隔离 (Thread Safety & Isolation)](#1. 线程安全隔离 (Thread Safety & Isolation))
- [2. 参数隐式传递 (Avoid Parameter Tunneling)](#2. 参数隐式传递 (Avoid Parameter Tunneling))
- 它的内部是怎么工作的?
- 必须警惕的坑:内存泄漏
简单来说, ThreadLocal 是 Java 提供的一种用于实现 线程本地变量 的机制。你可以把它想象成给每个线程分配了一个"私人储物柜"。
在多线程环境下,如果你希望每个线程都能保存一份属于自己的数据,且这个数据对其他线程不可见,那么 ThreadLocal 就是最佳选择。
核心解决的两个大痛点
1. 线程安全隔离 (Thread Safety & Isolation)
在并发编程中,最头疼的就是多个线程竞争同一个共享变量。传统的做法是加锁(synchronized 或 Lock),但加锁会带来性能损耗。
- 痛点: 多个线程共享一个变量,必须排队访问,性能差且容易出 Bug。
- ThreadLocal 的解法: 它不解决"竞争",而是直接消除"共享"。它给每个线程都发了一份变量的副本。既然大家用的都是自己的,互不干扰,自然就不需要加锁了。
- 典型场景: 数据库连接
Connection的管理、SimpleDateFormat(它是非线程安全的)的复用。
2. 参数隐式传递 (Avoid Parameter Tunneling)
这是它在架构设计中最常用的黑科技。
- 痛点: 假设你的调用链路很长:
Controller -> ServiceA -> ServiceB -> Dao。如果你需要在Dao层用到Controller里的用户信息,你得在每个方法参数里都传一个User对象。这叫"代码污染",改一个地方全链条都要改。 - ThreadLocal 的解法: 在
Controller层直接把用户信息塞进ThreadLocal。由于整个请求(Request)通常是在同一个线程里执行的,底层的Dao只需要从ThreadLocal里"摸"出来就行了。 - 典型场景: 存储当前登录用户信息、多数据源切换时的
DataSourceKey(就像你之前聊到的"社保库"与"人才库"切换)。
它的内部是怎么工作的?
虽然名字叫 ThreadLocal,但数据其实并不是存在 ThreadLocal 对象里的,而是存在 Thread 对象 内部的一个名为 threadLocals 的 Map(即 ThreadLocalMap)里。
- Key: 是当前的
ThreadLocal实例对象(且是弱引用)。 - Value: 才是你真正存进去的数据(比如
String类型的库名或User对象)。
形象比喻:
线程是"人",
ThreadLocalMap是人背着的"书包"。ThreadLocal实例就是书包里的"隔层标签"。当你调用get()时,线程会翻开自己的书包,找到对应的标签,取出里面的东西。
必须警惕的坑:内存泄漏
这是面试必考点。由于 ThreadLocalMap 的生命周期和线程一样长,如果你使用的是线程池 (线程是复用的),而你在用完数据后没有调用 .remove() 清理:
- Value 不会被回收: 下一个请求进来时,可能会读到上一个请求残留的数据。
- 内存占用: 随着线程池运行时间变长,不再使用的对象会一直霸占内存,最终导致 OOM(内存溢出)。
一句话总结:
ThreadLocal 是为了在不改变方法签名 的前提下,优雅地实现线程私有数据的传递与隔离。
你在实际项目中,除了用来做数据源切换,还有尝试过用它来处理用户的登录上下文(Session/Token)吗?