ThreadLocal 为同一个变量提供每线程独立的绑定,适合请求上下文等信息。它不是给共享变量加锁,也不负责自动跨线程传递数据。
值存在哪里?
常见 JDK 实现中,每个 Thread 持有 ThreadLocalMap,ThreadLocal 作为 key,对应 value 存在线程自己的 Map 中。
线程 A → 自己的 ThreadLocalMap → requestId = A-001线程 B → 自己的 ThreadLocalMap → requestId = B-001同一个 ThreadLocal 在不同线程访问不同绑定。但若两个线程把同一个可变对象设为 value,对象仍被共享,不能自动变成线程安全。
为什么可能内存泄漏?
ThreadLocalMap 的 key 是 ThreadLocal 的弱引用,value 是强引用。当 ThreadLocal 无其他强引用时,key 可能被 GC 清除,但 value 仍由存活线程的 Map 持有。
Map 会在部分访问时清理失效条目,不保证业务结束后立即清理。线程池线程长期存活,未 remove 的 value 可能长期驻留。
即使 ThreadLocal 仍被 static 字段强引用,忘记清理也会造成 value 长期占用内存;风险不只来自 key 变成 null。
为什么线程池中必须清理?
请求 A 设置用户信息后不清理,请求 B 使用同一个工作线程时,可能读到 A 的残留信息。这既是内存问题,也会造成上下文串用。
private static final ThreadLocal<String> USER = new ThreadLocal<>();
static void handle(String userId) { try { USER.set(userId); // 在当前线程内执行业务 } finally { USER.remove(); }}remove 只清理当前线程的绑定。嵌套上下文还要考虑恢复外层值,不能不加区分地清空调用方上下文。
会自动传到异步线程吗?
普通 ThreadLocal 不会。InheritableThreadLocal 可以在创建子线程时继承值,但池中线程通常早已创建,不会在每次提交任务时重新继承。
跨线程优先明确传参;统一上下文方案应在提交时捕获,执行时设置,并在 finally 恢复或清理。不要假设异步框架处理了所有 ThreadLocal。
面试回答模板
ThreadLocal 的值存在每个线程自己的 ThreadLocalMap 中。key 是弱引用,value 是强引用,长期存活的线程可能持有未清理的 value。线程池复用还可能造成上下文串用,因此业务结束时应在 finally 中 remove。普通 ThreadLocal 不自动跨线程传递,异步任务需要明确的传递与清理方案。
自测
- 弱引用 key 是否保证 value 及时回收?不是。
- ThreadLocal 能自动保护共享对象的并发修改吗?不能。
- InheritableThreadLocal 能保证每次线程池任务取得最新请求值吗?不能。