西游找找找
102.75M · 2026-02-07
ThreadLocal 为每个线程提供独立的变量副本,实现线程间数据隔离。其核心价值在于:
// Thread 类持有 ThreadLocalMap(JDK 1.5 起定型,JDK 8+ 无架构变化)
public class Thread {
ThreadLocal.ThreadLocalMap threadLocals = null; // 当前线程的本地变量容器
}
// ThreadLocalMap 内部结构
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 实际存储的值(强引用!)
Entry(ThreadLocal<?> k, Object v) {
super(k); // key 是弱引用
value = v;
}
}
private Entry[] table; // 哈希桶数组(初始容量 16)
}
关键设计:
Thread → 持有 → ThreadLocalMap → 存储 → <ThreadLocal实例(弱引用), value>
优势:线程销毁时,整个 Map 自动回收,避免全局 Map 的内存压力
| 方法 | 执行逻辑 |
|---|---|
get() | 1. 获取当前线程的 ThreadLocalMap2. 以当前 ThreadLocal 为 key 定位 Entry3. 若 key 为 null(stale entry),触发 expungeStaleEntry 清理 |
set(T value) | 1. 获取/创建 ThreadLocalMap2. 计算哈希索引( threadLocalHashCode 斐波那契哈希)3. 插入或替换 Entry,触发清理或扩容(阈值:len * 2/3) |
remove() | 1. 移除当前 ThreadLocal 对应的 Entry 2. 调用 expungeStaleEntry 清理连续 stale entries |
threadLocalHashCode = (nextHashCode.getAndAdd(0x61c88647))| 泄漏类型 | 产生原因 | 弱引用的作用 |
|---|---|---|
| ThreadLocal 对象泄漏 | 外部无强引用,但 Map 持有强引用 → 对象无法回收 | 弱引用直接解决:GC 可回收 ThreadLocal 对象 |
| value 值泄漏 | Entry.value 是强引用,线程长期存活且未清理 | 弱引用无法解决:需依赖清理机制 + 开发者 remove() |
// 场景:方法内创建临时 ThreadLocal
void process() {
ThreadLocal<User> local = new ThreadLocal<>(); // 临时变量
local.set(new User("张三"));
// 方法结束,local 变量出栈(外部无强引用)
// → GC 时:ThreadLocal 对象被回收(因 key 是弱引用)→ Entry.key = null
// → 后续 get/set/remove 触发清理 → 释放 value
}
设计精妙处:
弱引用将 “ThreadLocal 对象回收” 与 “value 清理” 解耦:
| 引用类型 | 结果 | 原因 |
|---|---|---|
| 强引用 | 灾难 | ThreadLocal 对象永久驻留内存 |
| 软引用 | 不合适 | 内存不足才回收,滞留时间过长 |
| 虚引用 | 不可行 | 无法获取对象,实现复杂 |
| 弱引用 | 最优 | GC 时立即回收,精准标记 stale entry |
public class UserContext {
private static final ThreadLocal<User> CURRENT_USER =
ThreadLocal.withInitial(() -> new User("anonymous"));
public static void setUser(User user) { CURRENT_USER.set(user); }
public static User getUser() { return CURRENT_USER.get(); }
public static void clear() { CURRENT_USER.remove(); } // 【关键】
}
// Spring MVC 拦截器中使用
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
User user = TokenUtil.parse(req); // 解析 token
UserContext.setUser(user);
return true;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
UserContext.clear(); // 【必须】请求结束清理!
}
}
// JDK 8+ 推荐使用 DateTimeFormatter(线程安全),此处仅作示例
private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public String format(Date date) {
return DATE_FORMATTER.get().format(date); // 每个线程独享实例
}
public class ConnectionHolder {
private static final ThreadLocal<Connection> CONN_HOLDER = new ThreadLocal<>();
public static void setConnection(Connection conn) { CONN_HOLDER.set(conn); }
public static Connection getConnection() { return CONN_HOLDER.get(); }
public static void remove() { CONN_HOLDER.remove(); }
}
// 事务管理器中
try {
Connection conn = dataSource.getConnection();
ConnectionHolder.setConnection(conn);
conn.setAutoCommit(false);
// ... 业务逻辑
conn.commit();
} finally {
ConnectionHolder.remove(); // 【必须】
// 关闭连接等资源
}
// 错误:线程复用导致数据污染
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> {
UserContext.setUser(new User("A"));
System.out.println(UserContext.getUser().getName()); // 可能输出 "B"!
});
// 正确:使用后立即清理
pool.submit(() -> {
try {
UserContext.setUser(new User("A"));
// 业务逻辑...
} finally {
UserContext.clear(); // 保证清理
}
});
remove()
try (var ctx = UserContext.push(user)) { ... }private static final ThreadLocal<...> HOLDER = ... 避免重复创建withInitial()
| 陷阱 | 后果 | 解决方案 |
|---|---|---|
| 线程池中未 remove | 数据污染 + 内存泄漏 | finally 块中 clear |
| 子线程未传递上下文 | 异步场景 traceId 丢失 | 使用 TransmittableThreadLocal(阿里开源) |
| 误用 InheritableThreadLocal | 线程池中数据错乱 | 仅用于新建线程,线程池禁用 |
| 依赖 GC 自动清理 value | 内存泄漏 | 主动 remove + 理解清理机制触发条件 |
ThreadLocalMap$Entry 残留MDC.putCloseable)| 维度 | 核心要点 |
|---|---|
| 设计本质 | “以空间换时间”:每个线程独占副本,消灭共享竞争 |
| 弱引用作用 | 解决 ThreadLocal 对象 泄漏,非 value 泄漏 |
| 开发者责任 | “谁设置,谁清理” —— remove() 是生命线 |
| 适用边界 | 适合线程生命周期明确的场景;线程池需格外谨慎 |
| 现代替代 | 简单场景:方法参数传递;异步场景:TransmittableThreadLocal;日期格式:DateTimeFormatter |
延伸阅读
java.lang.ThreadLocal(对比 JDK 8 与 JDK 21 无架构差异)Web文档的"Office时刻":jitword共建版1.0正式发布,让浏览器变成本地生产力
从 Token 生成到 SSE 推送,NestJS 打造丝滑 AI 对话体验
2026-02-07
2026-02-07