1. 是什么
ThreadLocal 是线程本地变量,给每个线程提供独立的变量副本,互不影响。
2. 简单用法
java
public class UserContext {
private static final ThreadLocal<String> USER = new ThreadLocal<>();
public static void set(String user) {
USER.set(user);
}
public static String get() {
return USER.get();
}
public static void remove() {
USER.remove();
}
}
// 使用
UserContext.set("张三");
String user = UserContext.get(); // 同一个线程里拿到的都是"张三"
UserContext.remove();
3. 常见场景
用户信息传递
不用每次方法参数传递:
java
// 登录时存进去
UserContext.set(SecurityContext.getUser());
// 任何地方都能取到
public void anyMethod() {
User user = UserContext.get(); // 拿到当前登录用户
}
数据库连接
保证同一线程用的是同一个连接:
java
private static final ThreadLocal<Connection> DB = new ThreadLocal<>();
// 获取连接
public Connection getConnection() {
Connection conn = DB.get();
if (conn == null) {
conn = dataSource.getConnection();
DB.set(conn);
}
return conn;
}
请求Tracer
java
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
public void handleRequest() {
TRACE_ID.set(UUID.randomUUID().toString());
log.info("开始处理, traceId={}", TRACE_ID.get());
// 整个调用链都能拿到同一个 traceId
}
4. 原理
每个 Thread 对象里有个 ThreadLocalMap:
Thread
└── threadLocals: ThreadLocalMap
├── ThreadLocal<?> → value
├── ThreadLocal<?> → value
└── ...
set 时,把 ThreadLocal 对象作为 key,存到当前线程的 Map 里。
get 时,从当前线程的 Map 里,用 ThreadLocal 对象作为 key 取值。
所以不同线程访问同一个 ThreadLocal,拿到的值不一样。
5. 内存泄漏
ThreadLocalMap 的 Entry 继承 WeakReference:
java
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
key(ThreadLocal 对象)是弱引用,理论上会被 GC 回收。但 value 是强引用,可能造成泄漏。
所以用完要 remove!
java
try {
User user = UserContext.get();
} finally {
UserContext.remove(); // 用完清理
}
6. 总结
- 每个线程独立副本,互不干扰
- 常用场景:用户信息传递、数据库连接、链路追踪
- 用完记得 remove,防止内存泄漏