接着ThreadLocal 深度解析(上)的内容。
第四章:ThreadLocal 生产实践
1. 使用规范
1.1 声明规范
ThreadLocal的声明方式直接影响其生命周期和内存使用。错误的声明方式是导致内存泄漏的常见原因之一。
核心原则 :ThreadLocal应该声明为static final类型。这确保了整个应用中只有一个ThreadLocal实例,避免了重复创建带来的内存问题。
java
/**
* 推荐:static final修饰
* 原因:确保ThreadLocal实例唯一,避免重复创建
*/
public class UserContext {
private static final ThreadLocal<User> HOLDER = new ThreadLocal<>();
}
/**
* 避免:非静态声明
* 问题:每次创建对象都会new一个ThreadLocal,导致内存泄漏
*/
public class BadExample {
private ThreadLocal<User> holder = new ThreadLocal<>(); // 危险!
}
/**
* 避免:方法内声明
* 问题:每次调用都创建新实例,之前的值无法访问也无法清理
*/
public void badMethod() {
ThreadLocal<String> local = new ThreadLocal<>(); // 危险!
local.set("value");
// local出了方法作用域,但Entry仍在ThreadLocalMap中
}
1.2 初始化规范
java
/**
* 推荐:使用withInitial提供初始值
*/
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private static final ThreadLocal<List<String>> BUFFER =
ThreadLocal.withInitial(ArrayList::new);
/**
* 可选:重写initialValue方法
*/
private static final ThreadLocal<StringBuilder> STRING_BUILDER =
new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
return new StringBuilder(256); // 预分配容量
}
};
/**
* 注意:初始值中不要引用外部可变状态
*/
// 错误:引用外部对象
private final Config config;
private static final ThreadLocal<Processor> PROCESSOR =
ThreadLocal.withInitial(() -> new Processor(config)); // 编译错误或运行时问题
// 正确:使用静态配置或无参构造
private static final ThreadLocal<Processor> PROCESSOR =
ThreadLocal.withInitial(Processor::new);
1.3 使用与清理规范
这是ThreadLocal使用中最重要的规范,也是最容易被忽视的。无论业务逻辑多么复杂,无论代码路径有多少分支,都必须确保remove()方法最终被调用。
实现这一点的唯一可靠方式是使用try-finally结构。下面展示三种常用的模式,复杂度递增:
java
/**
* 标准模式:try-finally确保清理
* 适用于:简单场景,不涉及嵌套调用
*/
public void standardUsage() {
try {
USER_CONTEXT.set(currentUser);
// 执行业务逻辑
processRequest();
} finally {
USER_CONTEXT.remove(); // 必须清理
}
}
/**
* 增强模式:支持嵌套调用
*/
public void nestedSafeUsage() {
User previous = USER_CONTEXT.get(); // 保存之前的值
try {
USER_CONTEXT.set(currentUser);
processRequest();
} finally {
if (previous != null) {
USER_CONTEXT.set(previous); // 恢复之前的值
} else {
USER_CONTEXT.remove();
}
}
}
/**
* 工具方法:自动管理生命周期
*/
public static <T, R> R withContext(ThreadLocal<T> threadLocal, T value,
Supplier<R> action) {
T previous = threadLocal.get();
try {
threadLocal.set(value);
return action.get();
} finally {
if (previous != null) {
threadLocal.set(previous);
} else {
threadLocal.remove();
}
}
}
// 使用
String result = withContext(USER_CONTEXT, user, () -> {
return businessService.process();
});
2. 常见陷阱与解决方案
理论知识固然重要,但真正让开发者栽跟头的往往是那些"看起来没问题"的代码。
2.1 陷阱一:线程池中忘记清理
这是ThreadLocal最常见、危害最大的陷阱。几乎所有使用ThreadLocal的生产事故都与此有关。
问题描述:线程池中的线程会被复用,如果不清理ThreadLocal,值会残留到下一个任务。这可能导致数据错乱、权限泄露等严重问题。
java
/**
* 问题代码
*/
public class ThreadPoolTrap {
private static final ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
public void process(String requestId) {
executor.submit(() -> {
REQUEST_ID.set(requestId);
doSomething();
// 忘记remove,下一个任务可能读到这个值
});
}
}
/**
* 解决方案
*/
public class ThreadPoolFixed {
private static final ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();
public void process(String requestId) {
executor.submit(() -> {
try {
REQUEST_ID.set(requestId);
doSomething();
} finally {
REQUEST_ID.remove(); // 必须清理
}
});
}
}
2.2 陷阱二:数据污染(脏读)
问题描述:由于线程复用,读取到上一个任务遗留的值。
java
/**
* 真实案例:用户权限错乱
*/
public class DataPollutionCase {
private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();
// 场景:用户A的请求处理完后没有清理
// 结果:用户B的请求复用了同一个线程,读取到用户A的ID
public void handleRequest(HttpServletRequest request) {
// 如果没有重新set,可能读到上一个用户的ID
String userId = USER_ID.get(); // 可能是用户A的ID!
// 权限检查...基于错误的userId
}
/**
* 正确做法:每次请求都重新设置
*/
public void handleRequestFixed(HttpServletRequest request) {
try {
String userId = extractUserId(request);
USER_ID.set(userId); // 明确设置当前请求的userId
// 业务处理
processRequest();
} finally {
USER_ID.remove();
}
}
}
2.3 陷阱三:父子线程值传递失败
问题描述:子线程无法获取父线程的ThreadLocal值。
java
/**
* 问题代码
*/
public class ParentChildTrap {
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
public void asyncProcess() {
TRACE_ID.set("trace-001");
new Thread(() -> {
// 子线程获取不到父线程的值
String traceId = TRACE_ID.get(); // null!
System.out.println("子线程TraceId: " + traceId);
}).start();
}
}
/**
* 解决方案1:使用InheritableThreadLocal
*/
public class ParentChildFixed1 {
// InheritableThreadLocal可以传递给子线程
private static final ThreadLocal<String> TRACE_ID = new InheritableThreadLocal<>();
public void asyncProcess() {
TRACE_ID.set("trace-001");
new Thread(() -> {
String traceId = TRACE_ID.get(); // "trace-001"
System.out.println("子线程TraceId: " + traceId);
}).start();
}
}
/**
* 解决方案2:手动传递值(更推荐)
*/
public class ParentChildFixed2 {
public void asyncProcess() {
String traceId = "trace-001";
// 通过参数或闭包传递
new Thread(() -> {
// 直接使用traceId变量
System.out.println("子线程TraceId: " + traceId);
}).start();
}
}
2.4 陷阱四:InheritableThreadLocal + 线程池
很多开发者在遇到陷阱三后,会尝试使用InheritableThreadLocal来解决问题。这确实能解决普通子线程的值传递问题,但在线程池场景下又会遇到新的陷阱。
问题描述 :InheritableThreadLocal只在线程创建时复制父线程的值。线程池中的线程是复用的,不会每次都重新创建,因此后续任务会读取到第一次创建时继承的旧值。
java
/**
* 问题代码
*/
public class ITLThreadPoolTrap {
private static final InheritableThreadLocal<String> CONTEXT =
new InheritableThreadLocal<>();
private static final ExecutorService executor = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws Exception {
// 第一次:设置值并提交任务
CONTEXT.set("request-1");
executor.submit(() -> {
System.out.println("任务1: " + CONTEXT.get()); // "request-1"
}).get();
// 第二次:更新值并提交任务
CONTEXT.set("request-2");
executor.submit(() -> {
// 仍然是"request-1"!因为线程复用,不会重新继承
System.out.println("任务2: " + CONTEXT.get()); // "request-1"
}).get();
}
}
/**
* 解决方案:使用TransmittableThreadLocal(阿里开源)
* 详见第05章
*/
2.5 陷阱五:异常导致finally不执行
我们一直强调使用try-finally来确保remove()被调用,但你是否知道,finally并不是100%可靠的?在某些极端情况下,finally块根本不会执行。
问题描述 :虽然罕见,但以下情况会导致finally不执行,从而造成ThreadLocal无法清理:
java
/**
* finally不执行的情况:
* 1. System.exit()
* 2. Runtime.halt()
* 3. 守护线程被强制终止
* 4. 无限循环/死锁在try块中
* 5. JVM崩溃
*/
public class FinallyTrap {
public void riskyMethod() {
try {
CONTEXT.set(value);
// 如果这里调用System.exit(0),finally不会执行
if (shouldExit) {
System.exit(0);
}
processRequest();
} finally {
CONTEXT.remove(); // 可能不执行
}
}
}
/**
* 解决方案:使用ShutdownHook清理
*/
public class FinallyFixed {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// 清理静态ThreadLocal
CONTEXT.remove();
}));
}
}
3. 生产级工具类
既然ThreadLocal使用起来这么容易出错,为什么不封装一些工具类来简化使用呢?本节提供三个生产环境验证过的工具类,你可以直接在项目中使用或根据需要进行修改。
这些工具类的设计目标是:让正确使用ThreadLocal变得简单,让错误使用变得困难。
3.1 线程上下文管理器
当一个应用中有多个ThreadLocal时,逐个清理非常繁琐且容易遗漏。ThreadContextManager提供了统一管理多个ThreadLocal的能力:
java
/**
* 通用线程上下文管理器
* 支持多个ThreadLocal的统一管理
*/
public class ThreadContextManager {
private static final List<ThreadLocal<?>> MANAGED_LOCALS = new ArrayList<>();
/**
* 注册需要管理的ThreadLocal
*/
public static void register(ThreadLocal<?> threadLocal) {
MANAGED_LOCALS.add(threadLocal);
}
/**
* 清理所有已注册的ThreadLocal
*/
public static void clearAll() {
for (ThreadLocal<?> local : MANAGED_LOCALS) {
local.remove();
}
}
/**
* 获取当前上下文快照
*/
public static Map<ThreadLocal<?>, Object> snapshot() {
Map<ThreadLocal<?>, Object> snapshot = new HashMap<>();
for (ThreadLocal<?> local : MANAGED_LOCALS) {
Object value = local.get();
if (value != null) {
snapshot.put(local, value);
}
}
return snapshot;
}
/**
* 恢复上下文
*/
@SuppressWarnings("unchecked")
public static void restore(Map<ThreadLocal<?>, Object> snapshot) {
clearAll();
for (Map.Entry<ThreadLocal<?>, Object> entry : snapshot.entrySet()) {
((ThreadLocal<Object>) entry.getKey()).set(entry.getValue());
}
}
}
3.2 安全的ThreadLocal包装器
如果你希望在执行完操作后自动清理ThreadLocal,而不用每次都写try-finally,可以使用这个包装器。它还支持在清理时执行自定义的资源释放逻辑(如关闭连接):
java
/**
* 自动清理的ThreadLocal包装器
*/
public class AutoCleanThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<T> initializer;
private final Consumer<T> cleaner;
public AutoCleanThreadLocal(Supplier<T> initializer) {
this(initializer, null);
}
public AutoCleanThreadLocal(Supplier<T> initializer, Consumer<T> cleaner) {
this.initializer = initializer;
this.cleaner = cleaner;
}
@Override
protected T initialValue() {
return initializer != null ? initializer.get() : null;
}
@Override
public void remove() {
T value = get();
if (cleaner != null && value != null) {
cleaner.accept(value); // 执行清理逻辑
}
super.remove();
}
/**
* 执行操作并自动清理
*/
public <R> R executeAndClean(Function<T, R> action) {
try {
return action.apply(get());
} finally {
remove();
}
}
}
// 使用示例
AutoCleanThreadLocal<Connection> connectionHolder = new AutoCleanThreadLocal<>(
() -> dataSource.getConnection(),
conn -> {
try { conn.close(); } catch (SQLException e) { /* log */ }
}
);
connectionHolder.executeAndClean(conn -> {
// 使用连接
return conn.prepareStatement(sql).executeQuery();
});
3.3 线程池任务包装器
在线程池场景中,我们经常需要将父线程的ThreadLocal值传递给工作线程。这个包装器可以自动完成上下文的捕获、传递和清理工作,是一个简化版的TransmittableThreadLocal:
java
/**
* 自动传递和清理ThreadLocal的任务包装器
*/
public class ContextAwareRunnable implements Runnable {
private final Runnable delegate;
private final Map<ThreadLocal<?>, Object> context;
public ContextAwareRunnable(Runnable delegate) {
this.delegate = delegate;
this.context = ThreadContextManager.snapshot(); // 捕获当前上下文
}
@Override
public void run() {
Map<ThreadLocal<?>, Object> previous = ThreadContextManager.snapshot();
try {
ThreadContextManager.restore(context); // 恢复上下文
delegate.run();
} finally {
ThreadContextManager.restore(previous); // 恢复原始上下文
}
}
/**
* 便捷包装方法
*/
public static Runnable wrap(Runnable runnable) {
return new ContextAwareRunnable(runnable);
}
public static <T> Callable<T> wrap(Callable<T> callable) {
return new ContextAwareCallable<>(callable);
}
}
// 使用
executor.submit(ContextAwareRunnable.wrap(() -> {
// 可以访问父线程的ThreadLocal值
String traceId = TRACE_CONTEXT.get();
}));
4. 框架集成最佳实践
在实际项目中,我们通常会使用Spring等框架。本节展示如何在主流框架中正确使用ThreadLocal,这些都是经过生产验证的模式。
4.1 Spring MVC集成
在Spring MVC中,最常见的ThreadLocal使用场景是在拦截器中设置请求上下文。关键点是:在preHandle中设置,在afterCompletion中清理(注意不是postHandle,因为postHandle在异常时不会执行):
java
/**
* Spring拦截器中使用ThreadLocal
*/
@Component
public class RequestContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 设置请求上下文
RequestContext context = new RequestContext();
context.setRequestId(generateRequestId());
context.setUserId(extractUserId(request));
context.setStartTime(System.currentTimeMillis());
RequestContextHolder.set(context);
// 设置MDC用于日志
MDC.put("requestId", context.getRequestId());
MDC.put("userId", context.getUserId());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
// 记录请求耗时
RequestContext context = RequestContextHolder.get();
if (context != null) {
long duration = System.currentTimeMillis() - context.getStartTime();
log.info("请求完成,耗时: {}ms", duration);
}
} finally {
// 清理所有上下文
RequestContextHolder.clear();
MDC.clear();
}
}
}
/**
* 请求上下文持有者
*/
public class RequestContextHolder {
private static final ThreadLocal<RequestContext> HOLDER = new ThreadLocal<>();
public static void set(RequestContext context) {
HOLDER.set(context);
}
public static RequestContext get() {
return HOLDER.get();
}
public static void clear() {
HOLDER.remove();
}
public static String getRequestId() {
RequestContext ctx = get();
return ctx != null ? ctx.getRequestId() : null;
}
}
4.2 MyBatis事务集成
java
/**
* 简化的事务管理器(演示用)
*/
public class SimpleTransactionManager {
private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<Boolean> TRANSACTION_ACTIVE = new ThreadLocal<>();
private final DataSource dataSource;
public void beginTransaction() throws SQLException {
if (Boolean.TRUE.equals(TRANSACTION_ACTIVE.get())) {
throw new IllegalStateException("事务已经开启");
}
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
CONNECTION_HOLDER.set(conn);
TRANSACTION_ACTIVE.set(true);
}
public void commit() throws SQLException {
Connection conn = CONNECTION_HOLDER.get();
if (conn != null) {
conn.commit();
}
}
public void rollback() {
Connection conn = CONNECTION_HOLDER.get();
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e) {
log.error("回滚失败", e);
}
}
}
public void close() {
try {
Connection conn = CONNECTION_HOLDER.get();
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
} catch (SQLException e) {
log.error("关闭连接失败", e);
} finally {
CONNECTION_HOLDER.remove();
TRANSACTION_ACTIVE.remove();
}
}
public Connection getConnection() {
return CONNECTION_HOLDER.get();
}
}
4.3 日志链路追踪集成
链路追踪是微服务架构中的标配功能。通过ThreadLocal配合日志框架的MDC,可以实现自动在每条日志中输出TraceId,而无需修改任何业务代码。这是一个非常优雅的设计模式:
java
/**
* 链路追踪上下文
*/
public class TraceContext {
private static final ThreadLocal<TraceInfo> HOLDER =
ThreadLocal.withInitial(TraceInfo::new);
public static TraceInfo get() {
return HOLDER.get();
}
public static void set(TraceInfo info) {
HOLDER.set(info);
// 同步到MDC
MDC.put("traceId", info.getTraceId());
MDC.put("spanId", info.getSpanId());
}
public static void clear() {
HOLDER.remove();
MDC.remove("traceId");
MDC.remove("spanId");
}
/**
* 创建新的追踪上下文
*/
public static TraceInfo newTrace() {
TraceInfo info = new TraceInfo();
info.setTraceId(generateTraceId());
info.setSpanId(generateSpanId());
set(info);
return info;
}
/**
* 从请求头中恢复追踪上下文
*/
public static TraceInfo fromHeaders(HttpServletRequest request) {
String traceId = request.getHeader("X-Trace-Id");
String parentSpanId = request.getHeader("X-Span-Id");
TraceInfo info = new TraceInfo();
info.setTraceId(traceId != null ? traceId : generateTraceId());
info.setParentSpanId(parentSpanId);
info.setSpanId(generateSpanId());
set(info);
return info;
}
}
@Data
public class TraceInfo {
private String traceId;
private String spanId;
private String parentSpanId;
}
5. 性能优化建议
虽然ThreadLocal的性能已经很好(get/set都是O(1)操作),但不正确的使用方式仍可能带来性能问题。本节分享几个常见的性能陷阱和优化建议。
5.0 性能基准测试数据
JMH基准测试代码:
java
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class ThreadLocalBenchmark {
private static final ThreadLocal<String> TL = ThreadLocal.withInitial(() -> "value");
private static final ThreadLocal<String> TL_EMPTY = new ThreadLocal<>();
@Setup(Level.Iteration)
public void setup() {
TL.set("benchmark-value");
}
@Benchmark
public String testGet() {
return TL.get();
}
@Benchmark
public void testSet() {
TL.set("new-value");
}
@Benchmark
public void testRemove() {
TL.remove();
}
@Benchmark
public void testSetThenRemove() {
TL.set("value");
TL.remove();
}
@TearDown(Level.Iteration)
public void tearDown() {
TL.remove();
}
}
5.1 避免频繁创建ThreadLocal
这是最常见的性能问题。每次创建新的ThreadLocal实例,都会在ThreadLocalMap中新增一个Entry,不仅浪费内存,还可能触发扩容和清理操作。
java
/**
* 性能问题:每次调用都创建ThreadLocal
*/
public String formatDate(Date date) {
ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>(); // 每次创建
local.set(new SimpleDateFormat("yyyy-MM-dd"));
return local.get().format(date);
}
/**
* 优化:静态复用
*/
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return DATE_FORMAT.get().format(date);
}
5.2 控制ThreadLocal数量
每个ThreadLocal实例都会在ThreadLocalMap中占用一个槽位。如果一个类中定义了大量的ThreadLocal,会增加哈希冲突的概率,降低访问效率。更好的做法是将多个相关的值封装到一个对象中:
java
/**
* 问题:过多的ThreadLocal
*/
public class TooManyThreadLocals {
private static final ThreadLocal<String> FIELD1 = new ThreadLocal<>();
private static final ThreadLocal<String> FIELD2 = new ThreadLocal<>();
private static final ThreadLocal<String> FIELD3 = new ThreadLocal<>();
// ... 更多字段
}
/**
* 优化:使用对象封装
*/
public class OptimizedContext {
private static final ThreadLocal<ContextData> CONTEXT = new ThreadLocal<>();
@Data
public static class ContextData {
private String field1;
private String field2;
private String field3;
}
}
5.3 延迟初始化大对象
使用withInitial()虽然方便,但对于大对象(如大数组、复杂对象)来说,可能造成不必要的内存浪费。如果不是每个线程都需要这个对象,应该采用延迟初始化的方式:
java
/**
* 问题:不需要时也创建大对象
*/
private static final ThreadLocal<byte[]> BUFFER =
ThreadLocal.withInitial(() -> new byte[1024 * 1024]); // 1MB
/**
* 优化:按需创建
*/
private static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<>();
public byte[] getBuffer() {
byte[] buffer = BUFFER.get();
if (buffer == null) {
buffer = new byte[1024 * 1024];
BUFFER.set(buffer);
}
return buffer;
}
第五章:ThreadLocal 进阶应用
1. InheritableThreadLocal详解
掌握了ThreadLocal的基本用法后,你可能会遇到一个新的问题:如何让子线程也能访问父线程的ThreadLocal值?这在异步编程中是一个常见的需求,比如在子线程中也需要访问用户上下文、TraceId等信息。
JDK提供了InheritableThreadLocal来解决这个问题,但它也有自己的局限性。本节将详细介绍它的原理、用法和注意事项。
1.1 为什么需要InheritableThreadLocal
先来看一个问题:普通ThreadLocal的值无法传递给子线程。这在很多场景下会造成困扰:
java
public class ThreadLocalInheritanceProblem {
private static final ThreadLocal<String> NORMAL = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
NORMAL.set("父线程的值");
Thread child = new Thread(() -> {
// 子线程无法获取父线程的值
System.out.println("子线程获取: " + NORMAL.get()); // null
});
child.start();
child.join();
}
}
InheritableThreadLocal解决了这个问题:
java
public class InheritableThreadLocalDemo {
private static final InheritableThreadLocal<String> INHERITABLE =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
INHERITABLE.set("父线程的值");
Thread child = new Thread(() -> {
// 子线程可以获取父线程的值
System.out.println("子线程获取: " + INHERITABLE.get()); // "父线程的值"
});
child.start();
child.join();
}
}
1.2 源码分析
InheritableThreadLocal继承自ThreadLocal,重写了三个方法:
java
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 子线程创建时,如何处理父线程的值
* 默认直接返回父线程的值(浅拷贝)
* 可以重写实现深拷贝
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 使用inheritableThreadLocals而非threadLocals
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 创建时使用inheritableThreadLocals
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
1.3 继承时机:Thread构造函数
值的传递发生在Thread的构造函数中:
java
// Thread.java 构造函数(简化)
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
// 如果父线程有inheritableThreadLocals,则复制给子线程
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
1.4 深拷贝 vs 浅拷贝
默认的childValue()返回原对象引用(浅拷贝),父子线程共享同一对象:
java
/**
* 浅拷贝问题演示
*/
public class ShallowCopyProblem {
private static final InheritableThreadLocal<List<String>> LIST =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
List<String> parentList = new ArrayList<>();
parentList.add("parent");
LIST.set(parentList);
Thread child = new Thread(() -> {
List<String> childList = LIST.get();
childList.add("child"); // 修改的是同一个对象!
});
child.start();
child.join();
// 父线程的List也被修改了
System.out.println(LIST.get()); // [parent, child]
}
}
/**
* 深拷贝解决方案(简单对象)
*/
public class DeepCopySolution {
private static final InheritableThreadLocal<List<String>> LIST =
new InheritableThreadLocal<List<String>>() {
@Override
protected List<String> childValue(List<String> parentValue) {
// 创建新的ArrayList,实现深拷贝
return parentValue != null ? new ArrayList<>(parentValue) : null;
}
};
}
注意 :上面的
new ArrayList<>(parentValue)只是浅拷贝List容器。如果List中的元素是可变对象,子线程修改元素内部状态仍会影响父线程!
递归深拷贝解决方案(复杂对象):
java
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 使用Jackson实现真正的递归深拷贝
*/
public class TrueDeepCopySolution {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final InheritableThreadLocal<UserContext> CONTEXT =
new InheritableThreadLocal<UserContext>() {
@Override
protected UserContext childValue(UserContext parentValue) {
if (parentValue == null) return null;
try {
// 序列化再反序列化,实现完全深拷贝
String json = MAPPER.writeValueAsString(parentValue);
return MAPPER.readValue(json, UserContext.class);
} catch (Exception e) {
throw new RuntimeException("深拷贝失败", e);
}
}
};
@Data
public static class UserContext implements Serializable {
private String userId;
private List<String> roles; // 可变集合
private Map<String, Object> attrs; // 可变Map
}
}
/**
* 或使用Cloneable接口(需要对象支持clone)
*/
public class CloneableDeepCopy {
private static final InheritableThreadLocal<CloneableContext> CONTEXT =
new InheritableThreadLocal<CloneableContext>() {
@Override
protected CloneableContext childValue(CloneableContext parentValue) {
return parentValue != null ? parentValue.deepClone() : null;
}
};
}
1.5 InheritableThreadLocal的局限性

核心问题:InheritableThreadLocal只在线程创建时复制,线程池复用线程时不会更新。
1.6 InheritableThreadLocal与虚拟线程
重要警告 :在JDK 21+的虚拟线程场景下,不推荐使用InheritableThreadLocal。
原因分析:
| 问题 | 说明 |
|---|---|
| 内存开销 | 每个虚拟线程都会复制一份父线程的inheritableThreadLocals |
| 复制时机 | 虚拟线程创建时复制,但虚拟线程可能在不同载体线程上执行 |
| 官方不推荐 | JDK官方文档明确指出虚拟线程不适合使用ThreadLocal家族 |
虚拟线程场景的替代方案:
java
// 不推荐:虚拟线程 + InheritableThreadLocal
private static final InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
ITL.set("parent-value");
executor.submit(() -> {
// 虽然能获取到值,但每个虚拟线程都有一份拷贝
// 百万虚拟线程 = 百万份拷贝 = 内存爆炸
System.out.println(ITL.get());
});
}
// 推荐:使用ScopedValue(JDK 21+)
private static final ScopedValue<String> SCOPED = ScopedValue.newInstance();
ScopedValue.where(SCOPED, "parent-value").run(() -> {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(() -> {
// ScopedValue天然支持结构化并发,无额外内存开销
System.out.println(SCOPED.get());
return null;
});
scope.join();
}
});
// 或者:显式参数传递
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
String contextValue = "parent-value"; // 捕获到lambda闭包
executor.submit(() -> {
// 通过闭包传递,简单可靠
System.out.println(contextValue);
});
}
InheritableThreadLocal使用建议:
| 场景 | 是否推荐 | 替代方案 |
|---|---|---|
| 普通new Thread() | 可用 | - |
| 传统线程池 | 不推荐 | TTL |
| ForkJoinPool | 谨慎 | 显式传参 |
| 虚拟线程 | 强烈不推荐 | ScopedValue / 显式传参 |
2. TransmittableThreadLocal
既然InheritableThreadLocal在线程池场景下失效,有没有更好的解决方案?
阿里巴巴开源了一个非常实用的库:TransmittableThreadLocal(简称TTL)。它在阿里内部经过大规模生产验证,是解决线程池上下文传递问题的事实标准。
2.1 什么是TransmittableThreadLocal
TransmittableThreadLocal(TTL)是阿里开源的增强版ThreadLocal。它的核心思想是:在提交任务时捕获当前线程的ThreadLocal值,在任务执行时恢复这些值,执行完毕后再清理。
2.2 工作原理
2.3 使用方法
Maven依赖:
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
方式一:修饰Runnable/Callable
java
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
public class TtlDemo1 {
private static final TransmittableThreadLocal<String> CONTEXT =
new TransmittableThreadLocal<>();
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(1);
// 第一次提交
CONTEXT.set("request-1");
executor.submit(TtlRunnable.get(() -> {
System.out.println("任务1: " + CONTEXT.get()); // "request-1"
})).get();
// 第二次提交(线程被复用)
CONTEXT.set("request-2");
executor.submit(TtlRunnable.get(() -> {
System.out.println("任务2: " + CONTEXT.get()); // "request-2"
})).get();
executor.shutdown();
}
}
方式二:修饰线程池
java
import com.alibaba.ttl.threadpool.TtlExecutors;
public class TtlDemo2 {
private static final TransmittableThreadLocal<String> CONTEXT =
new TransmittableThreadLocal<>();
public static void main(String[] args) throws Exception {
// 使用TtlExecutors包装线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(1)
);
CONTEXT.set("request-1");
executor.submit(() -> {
System.out.println("任务1: " + CONTEXT.get()); // "request-1"
}).get();
CONTEXT.set("request-2");
executor.submit(() -> {
// 无需TtlRunnable包装,自动传递
System.out.println("任务2: " + CONTEXT.get()); // "request-2"
}).get();
executor.shutdown();
}
}
2.4 TTL核心源码分析
java
// TtlRunnable核心逻辑(简化)
public final class TtlRunnable implements Runnable {
private final Runnable runnable;
// 捕获的TTL快照
private final Object captured;
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.runnable = runnable;
// 在构造时捕获当前线程的TTL值
this.captured = TransmittableThreadLocal.Transmitter.capture();
}
@Override
public void run() {
// 保存工作线程原有的TTL值
Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
// 执行实际任务
runnable.run();
} finally {
// 恢复工作线程原有的TTL值
TransmittableThreadLocal.Transmitter.restore(backup);
}
}
}
2.5 ThreadLocal家族对比
| 特性 | ThreadLocal | InheritableThreadLocal | TransmittableThreadLocal |
|---|---|---|---|
| 线程隔离 | ✅ | ✅ | ✅ |
| 子线程继承 | ❌ | ✅ | ✅ |
| 线程池传递 | ❌ | ❌ | ✅ |
| JDK内置 | ✅ | ✅ | ❌(第三方) |
| 性能开销 | 最低 | 低 | 中等 |
| 使用场景 | 普通场景 | 简单父子线程 | 线程池场景 |
2.6 异步场景上下文传递方案全景对比
在实际工程中,我们经常遇到需要在异步场景下传递上下文的需求。下面是各种方案的全面对比,帮助你做出正确的技术选型:
方案对比表:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 显式参数传递 | 简单异步调用 | 清晰、可测试、无魔法 | 侵入性高,需改方法签名 |
| Lambda闭包捕获 | 单次异步调用 | 简单直接 | 只适合简单场景 |
| ThreadLocal + 手动包装 | 线程池 | 无需引入依赖 | 需要手写包装器 |
| TTL | 线程池 | 成熟稳定、功能完善 | 需引入第三方依赖 |
| Spring TaskDecorator | Spring环境 | 与Spring生态集成好 | 限Spring环境 |
| ScopedValue | JDK 21+虚拟线程 | 官方方案、无泄漏风险 | 需JDK 21+ |
各方案代码示例:
java
// 方案1: 显式参数传递(最推荐)
public void processAsync(String userId, String traceId) {
executor.submit(() -> {
doProcess(userId, traceId); // 直接使用参数
});
}
// 方案2: Lambda闭包捕获
public void processWithClosure() {
String userId = UserContext.get();
String traceId = TraceContext.get();
executor.submit(() -> {
// userId和traceId通过闭包捕获
System.out.println("User: " + userId + ", Trace: " + traceId);
});
}
// 方案3: 手动包装Runnable
public class ContextAwareRunnable implements Runnable {
private final Runnable delegate;
private final String userId;
private final String traceId;
public ContextAwareRunnable(Runnable delegate) {
this.delegate = delegate;
this.userId = UserContext.get(); // 捕获
this.traceId = TraceContext.get(); // 捕获
}
@Override
public void run() {
try {
UserContext.set(userId); // 恢复
TraceContext.set(traceId); // 恢复
delegate.run();
} finally {
UserContext.remove(); // 清理
TraceContext.remove(); // 清理
}
}
}
// 方案4: Spring TaskDecorator
@Configuration
public class AsyncConfig {
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(runnable -> {
// 捕获当前上下文
String userId = UserContext.get();
String traceId = TraceContext.get();
Map<String, String> mdcContext = MDC.getCopyOfContextMap();
return () -> {
try {
// 恢复上下文
UserContext.set(userId);
TraceContext.set(traceId);
if (mdcContext != null) MDC.setContextMap(mdcContext);
runnable.run();
} finally {
// 清理
UserContext.remove();
TraceContext.remove();
MDC.clear();
}
};
});
executor.initialize();
return executor;
}
}
常见"不合法姿势":
| 错误做法 | 问题 | 正确做法 |
|---|---|---|
| 线程池中直接使用ThreadLocal.get() | 获取到的是工作线程的值,不是提交线程的 | 使用TTL或手动传递 |
| 使用InheritableThreadLocal解决线程池问题 | 只在线程创建时复制,复用时不更新 | 使用TTL |
| CompletableFuture中直接读ThreadLocal | 可能在ForkJoinPool线程执行,上下文丢失 | 手动传递或TTL |
| @Async方法中读ThreadLocal | 异步方法在新线程执行,上下文丢失 | TaskDecorator或TTL |
3. 主流框架中的应用
学习了ThreadLocal的原理和扩展后,让我们看看主流框架是如何使用ThreadLocal的。这些都是经过千锤百炼的实现,值得我们学习和借鉴。
通过分析这些框架的源码,你会发现ThreadLocal在企业级开发中的重要地位,也能学到很多设计技巧。
3.1 Spring框架
Spring框架中大量使用了ThreadLocal,其中最典型的是RequestContextHolder和SecurityContextHolder。
RequestContextHolder:存储HTTP请求上下文,让我们可以在Service层获取HttpServletRequest:
java
// Spring源码(简化)
public abstract class RequestContextHolder {
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
public static void setRequestAttributes(RequestAttributes attributes,
boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
} else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
} else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
}
使用示例:
java
@RestController
public class UserController {
@GetMapping("/user")
public User getUser() {
// 获取当前请求
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
// 从请求中获取信息
String token = request.getHeader("Authorization");
return userService.findByToken(token);
}
}
3.2 Spring Security
SecurityContextHolder:存储安全上下文
java
// Spring Security源码(简化)
public class SecurityContextHolder {
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
private static SecurityContextHolderStrategy strategy;
// ThreadLocal模式实现
final class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder =
new ThreadLocal<>();
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public void setContext(SecurityContext context) {
contextHolder.set(context);
}
public void clearContext() {
contextHolder.remove();
}
}
}
使用示例:
java
@Service
public class SecurityService {
public String getCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth != null ? auth.getName() : null;
}
// 异步任务中传递SecurityContext
@Async
public void asyncTask() {
// 默认情况下,异步任务获取不到SecurityContext
// 需要配置:
// SecurityContextHolder.setStrategyName(
// SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
String username = getCurrentUsername();
}
}
3.3 Spring事务管理
TransactionSynchronizationManager:存储事务资源
java
// Spring源码(简化)
public abstract class TransactionSynchronizationManager {
// 当前线程绑定的资源(如DataSource -> Connection)
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 当前线程绑定的事务同步器
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
// 当前事务名称
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 当前事务是否只读
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
// 当前事务隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
// 事务是否激活
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
/**
* 绑定资源到当前线程
*/
public static void bindResource(Object key, Object value) {
Map<Object, Object> map = resources.get();
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
map.put(key, value);
}
/**
* 获取当前线程绑定的资源
*/
public static Object getResource(Object key) {
Map<Object, Object> map = resources.get();
return map != null ? map.get(key) : null;
}
}
3.4 MyBatis
SqlSessionManager:存储SqlSession
java
// MyBatis源码(简化)
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
public void startManagedSession(boolean autoCommit) {
this.localSqlSession.set(openSession(autoCommit));
}
public boolean isManagedSessionStarted() {
return this.localSqlSession.get() != null;
}
public void clearManagedSession() {
this.localSqlSession.remove();
}
private SqlSession getSqlSession() {
SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("...");
}
return sqlSession;
}
}
3.5 Logback MDC
MDC(Mapped Diagnostic Context):存储日志上下文
java
// Logback MDC实现(简化)
public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<>();
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> oldMap = copyOnThreadLocal.get();
Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
} else {
oldMap.put(key, val);
}
}
public String get(String key) {
Map<String, String> map = copyOnThreadLocal.get();
return map != null ? map.get(key) : null;
}
public void remove(String key) {
Map<String, String> oldMap = copyOnThreadLocal.get();
if (oldMap != null) {
oldMap.remove(key);
}
}
public void clear() {
copyOnThreadLocal.remove();
}
}
使用示例:
java
// 在过滤器中设置
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);
MDC.put("userId", getUserId(request));
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
// logback.xml配置
// <pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n</pattern>
3.6 Dubbo RpcContext
在微服务架构中,Dubbo使用RpcContext在服务调用链中传递上下文信息。RpcContext内部也是基于ThreadLocal实现的:
java
// Dubbo RpcContext源码(简化)
public class RpcContext {
private static final InternalThreadLocal<RpcContext> LOCAL =
new InternalThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
};
// 附件信息,用于隐式传参
private final Map<String, Object> attachments = new HashMap<>();
public static RpcContext getContext() {
return LOCAL.get();
}
public RpcContext setAttachment(String key, Object value) {
attachments.put(key, value);
return this;
}
public Object getAttachment(String key) {
return attachments.get(key);
}
// 每次RPC调用后会自动清理
public static void removeContext() {
LOCAL.remove();
}
}
Dubbo中传递TraceId示例:
java
// 消费端Filter:传递TraceId
public class TraceIdConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
// 从当前上下文获取TraceId,设置到RpcContext
String traceId = TraceContext.getTraceId();
RpcContext.getContext().setAttachment("traceId", traceId);
return invoker.invoke(invocation);
}
}
// 提供端Filter:接收TraceId
public class TraceIdProviderFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
// 从RpcContext获取TraceId,设置到当前上下文
String traceId = (String) RpcContext.getContext().getAttachment("traceId");
if (traceId != null) {
TraceContext.setTraceId(traceId);
MDC.put("traceId", traceId);
}
try {
return invoker.invoke(invocation);
} finally {
TraceContext.clear();
MDC.remove("traceId");
}
}
}
3.7 安全警示:ThreadLocal中的敏感数据
安全警告:ThreadLocal中存储敏感数据需要特别注意!
在用户上下文传递场景中,如果ThreadLocal存储了敏感信息(如用户密码、Token、权限列表),线程池复用导致的数据污染可能引发严重的安全漏洞。
风险场景:
| 风险 | 描述 | 后果 |
|---|---|---|
| 权限绕过 | 用户A的权限信息残留,被用户B的请求读取 | 越权访问 |
| 数据泄露 | 用户A的敏感数据被用户B看到 | 隐私泄露 |
| 身份冒充 | 用户B以用户A的身份执行操作 | 审计失效 |
安全最佳实践:
java
// 危险:存储完整的敏感对象
public class UnsafeContext {
private static final ThreadLocal<User> USER = new ThreadLocal<>();
@Data
public static class User {
private String userId;
private String password; // 危险!
private String token; // 危险!
private List<String> roles; // 可能泄露
}
}
// 安全:最小化存储 + 脱敏
public class SafeContext {
private static final ThreadLocal<SafeUserInfo> USER = new ThreadLocal<>();
@Data
public static class SafeUserInfo {
private String userId; // 仅存储ID
private String username; // 脱敏后的用户名
// 不存储密码、token等敏感信息
// 权限信息每次从数据库/缓存实时获取
}
}
// 关键操作:双重校验
public void deleteUser(Long targetUserId) {
SafeUserInfo operator = SafeContext.USER.get();
// 1. ThreadLocal提供便利(但不作为唯一依据)
if (operator == null) {
throw new SecurityException("未登录");
}
// 2. 关键操作从权限服务重新校验(防止ThreadLocal污染)
boolean hasPermission = permissionService.checkPermission(
operator.getUserId(), "user:delete");
if (!hasPermission) {
throw new SecurityException("无权限");
}
userRepository.delete(targetUserId);
}
安全检查清单:
| 检查项 | 说明 |
|---|---|
| 不存储密码、Token等凭证 | 即使泄露也无法直接利用 |
| 最小化存储原则 | 只存储必要的非敏感字段 |
| 关键操作二次校验 | 不完全信任ThreadLocal的值 |
| 敏感操作审计日志 | 记录实际执行者信息 |
| 定期安全审查 | 检查ThreadLocal使用是否合规 |
4. 自定义ThreadLocal扩展
4.1 带过期时间的ThreadLocal
java
/**
* 支持过期时间的ThreadLocal
*/
public class ExpirableThreadLocal<T> extends ThreadLocal<T> {
private final ThreadLocal<Long> expirationTime = new ThreadLocal<>();
private final long ttlMillis;
public ExpirableThreadLocal(long ttlMillis) {
this.ttlMillis = ttlMillis;
}
@Override
public void set(T value) {
super.set(value);
expirationTime.set(System.currentTimeMillis() + ttlMillis);
}
@Override
public T get() {
Long expTime = expirationTime.get();
if (expTime != null && System.currentTimeMillis() > expTime) {
remove();
return null;
}
return super.get();
}
@Override
public void remove() {
super.remove();
expirationTime.remove();
}
}
// 使用
ExpirableThreadLocal<String> cache = new ExpirableThreadLocal<>(5000); // 5秒过期
cache.set("value");
Thread.sleep(6000);
cache.get(); // null,已过期
4.2 可观测的ThreadLocal
java
/**
* 支持监控的ThreadLocal
*/
public class ObservableThreadLocal<T> extends ThreadLocal<T> {
private static final AtomicLong setCount = new AtomicLong();
private static final AtomicLong getCount = new AtomicLong();
private static final AtomicLong removeCount = new AtomicLong();
private final String name;
public ObservableThreadLocal(String name) {
this.name = name;
}
@Override
public void set(T value) {
setCount.incrementAndGet();
log.debug("[{}] set called, value: {}", name, value);
super.set(value);
}
@Override
public T get() {
getCount.incrementAndGet();
T value = super.get();
log.debug("[{}] get called, value: {}", name, value);
return value;
}
@Override
public void remove() {
removeCount.incrementAndGet();
log.debug("[{}] remove called", name);
super.remove();
}
public static Map<String, Long> getMetrics() {
Map<String, Long> metrics = new HashMap<>();
metrics.put("setCount", setCount.get());
metrics.put("getCount", getCount.get());
metrics.put("removeCount", removeCount.get());
return metrics;
}
}
4.3 类型安全的上下文
java
/**
* 类型安全的上下文Key
*/
public final class ContextKey<T> {
private final String name;
private final Class<T> type;
private ContextKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
public static <T> ContextKey<T> of(String name, Class<T> type) {
return new ContextKey<>(name, type);
}
public String getName() { return name; }
public Class<T> getType() { return type; }
}
/**
* 类型安全的ThreadLocal上下文
*/
public class TypedThreadContext {
private static final ThreadLocal<Map<ContextKey<?>, Object>> CONTEXT =
ThreadLocal.withInitial(HashMap::new);
public static <T> void set(ContextKey<T> key, T value) {
CONTEXT.get().put(key, value);
}
@SuppressWarnings("unchecked")
public static <T> T get(ContextKey<T> key) {
return (T) CONTEXT.get().get(key);
}
public static <T> void remove(ContextKey<T> key) {
CONTEXT.get().remove(key);
}
public static void clear() {
CONTEXT.remove();
}
}
// 使用
ContextKey<String> USER_ID = ContextKey.of("userId", String.class);
ContextKey<Integer> TENANT_ID = ContextKey.of("tenantId", Integer.class);
TypedThreadContext.set(USER_ID, "user-001");
TypedThreadContext.set(TENANT_ID, 123);
String userId = TypedThreadContext.get(USER_ID); // 类型安全
Integer tenantId = TypedThreadContext.get(TENANT_ID); // 类型安全
5. ThreadLocal vs 其他方案对比
5.1 ThreadLocal vs 参数传递
| 维度 | ThreadLocal | 参数传递 |
|---|---|---|
| 侵入性 | 低,无需修改方法签名 | 高,需要修改所有方法 |
| 可读性 | 低,隐式依赖 | 高,依赖显式 |
| 调试难度 | 高,值来源不明显 | 低,可追踪 |
| 性能 | O(1)哈希访问 | 方法调用开销 |
| 适用场景 | 横切关注点(日志、安全) | 业务数据流转 |
5.2 ThreadLocal vs Scope Bean(Spring)
java
// ThreadLocal方式
@Component
public class RequestContext {
private static final ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();
public void setRequestId(String id) { REQUEST_ID.set(id); }
public String getRequestId() { return REQUEST_ID.get(); }
public void clear() { REQUEST_ID.remove(); }
}
// Spring Request Scope方式
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedContext {
private String requestId;
public void setRequestId(String id) { this.requestId = id; }
public String getRequestId() { return this.requestId; }
// 不需要手动清理,Spring自动管理
}
| 维度 | ThreadLocal | Scope Bean |
|---|---|---|
| 生命周期管理 | 手动 | 自动 |
| 框架依赖 | 无 | 需要Spring |
| 异步传递 | 需要处理 | 需要处理 |
| 适用范围 | 任何场景 | Web请求场景 |
5.3 选型建议

6. JDK版本演进
6.1 版本特性对比
| JDK版本 | 新增特性 |
|---|---|
| JDK 1.2 | ThreadLocal首次引入 |
| JDK 1.2 | InheritableThreadLocal引入 |
| JDK 1.4 | ThreadLocalMap使用弱引用 |
| JDK 1.5 | 增加remove()方法 |
| JDK 1.8 | 增加withInitial()工厂方法 |
| JDK 1.8 | SuppliedThreadLocal内部类 |
6.2 JDK 8+ 推荐写法
java
// JDK 8之前
private static final ThreadLocal<SimpleDateFormat> OLD_STYLE =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// JDK 8+ 推荐
private static final ThreadLocal<SimpleDateFormat> NEW_STYLE =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 方法引用
private static final ThreadLocal<StringBuilder> BUILDER =
ThreadLocal.withInitial(StringBuilder::new);
6.3 Virtual Threads(JDK 21+)与ThreadLocal
JDK 21正式引入了虚拟线程(Virtual Threads),这对ThreadLocal的使用带来了重大影响。
虚拟线程的特点:
- 数量可能非常大(百万级)
- 生命周期通常很短
- 由JVM调度,可能在不同的载体线程(carrier thread)上运行
ThreadLocal在虚拟线程中的问题:
| 问题 | 影响 | 严重程度 |
|---|---|---|
| 内存占用 | 百万虚拟线程 × N个ThreadLocal = 巨大内存开销 | 严重 |
| 频繁创建/销毁 | 虚拟线程生命周期短,ThreadLocal初始化开销累积 | 中等 |
| 载体线程切换 | 虚拟线程可能在不同载体线程上恢复执行 | 低(JVM处理) |
java
// 虚拟线程中的问题示例
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
// 每个虚拟线程都有独立的ThreadLocalMap
// 100万个虚拟线程 = 100万份ThreadLocal副本!
USER_CONTEXT.set(currentUser);
try {
processRequest();
} finally {
USER_CONTEXT.remove();
}
});
}
}
工程建议 :在虚拟线程场景下,优先考虑显式参数传递 或ScopedValue。
6.4 ScopedValue(JDK 21+ 预览特性)
ScopedValue是JDK 21引入的预览特性(JDK 23正式发布),专门设计用于替代ThreadLocal在特定场景下的使用。
ScopedValue vs ThreadLocal 对比:
| 维度 | ThreadLocal | ScopedValue |
|---|---|---|
| 生命周期 | 绑定线程,需手动清理 | 绑定代码块/作用域,自动清理 |
| 可变性 | 可变(可set多次) | 不可变(一次绑定) |
| 继承性 | 需要InheritableThreadLocal | 天然支持结构化并发 |
| 内存泄漏风险 | 高(忘记remove) | 无(作用域结束自动释放) |
| 虚拟线程支持 | 内存开销大 | 优化设计,开销小 |
| 可见性 | 线程内全局可见 | 仅在绑定的作用域内可见 |
ScopedValue使用示例(JDK 21+ 预览特性):
java
import jdk.incubator.concurrent.ScopedValue; // JDK 21预览
// import java.lang.ScopedValue; // JDK 23+正式版
public class ScopedValueDemo {
// 声明ScopedValue(类似ThreadLocal的声明)
private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
public void handleRequest(String userId) {
// 绑定值并执行代码块
ScopedValue.where(USER_ID, userId).run(() -> {
// 在此作用域内,USER_ID.get() 返回 userId
processRequest();
// 作用域结束,自动清理,无需手动remove!
});
}
private void processRequest() {
// 可以在调用链任意位置获取值
String userId = USER_ID.get();
System.out.println("处理用户: " + userId);
// 支持嵌套作用域
ScopedValue.where(USER_ID, "nested-user").run(() -> {
System.out.println("嵌套作用域: " + USER_ID.get()); // "nested-user"
});
// 嵌套结束后,恢复原值
System.out.println("恢复: " + USER_ID.get()); // userId
}
// 支持带返回值的调用
public String processWithResult(String userId) {
return ScopedValue.where(USER_ID, userId).call(() -> {
return "处理结果: " + USER_ID.get();
});
}
}
何时使用ScopedValue替代ThreadLocal:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 只读上下文传递(如userId, traceId) | ScopedValue | 不可变,无泄漏风险 |
| 可变状态绑定线程(如Connection) | ThreadLocal | 需要可变性 |
| 虚拟线程场景 | ScopedValue | 内存优化 |
| 结构化并发(StructuredTaskScope) | ScopedValue | 天然支持 |
| 传统线程池 + 老项目 | ThreadLocal + TTL | 兼容性 |
6.5 从ThreadLocal迁移到ScopedValue
如果你的项目使用JDK 21+,并且满足以下条件,可以考虑迁移:
适合迁移的场景:
- ThreadLocal存储的是只读数据(如用户ID、请求ID)
- 值在请求/任务开始时设置,结束后不再需要
- 使用虚拟线程或计划使用
- 希望消除内存泄漏风险
迁移步骤:
java
// Step 1: 原ThreadLocal代码
public class OldStyle {
private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();
public void handle(String userId) {
try {
USER_ID.set(userId);
process();
} finally {
USER_ID.remove();
}
}
}
// Step 2: 迁移到ScopedValue
public class NewStyle {
private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
public void handle(String userId) {
ScopedValue.where(USER_ID, userId).run(this::process);
// 无需try-finally,自动清理!
}
}
// Step 3: 兼容模式(同时支持两种方式)
public class CompatibleStyle {
private static final ScopedValue<String> USER_ID_SCOPED = ScopedValue.newInstance();
private static final ThreadLocal<String> USER_ID_TL = new ThreadLocal<>();
public static String getUserId() {
// 优先从ScopedValue获取,回退到ThreadLocal
if (USER_ID_SCOPED.isBound()) {
return USER_ID_SCOPED.get();
}
return USER_ID_TL.get();
}
}
注意 :ScopedValue在JDK 21/22是预览特性,需要添加
--enable-preview参数。JDK 23+正式发布。
6.6 日期格式化:新旧方案对比
文章前面使用ThreadLocal<SimpleDateFormat>作为经典示例,这主要是为老项目准备的。新项目应直接使用DateTimeFormatter:
java
// 老项目方案(仍然有效,但不推荐新项目使用)
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatOld(Date date) {
return DATE_FORMAT.get().format(date);
}
// JDK 8+新项目推荐:DateTimeFormatter是线程安全的,无需ThreadLocal
private static final DateTimeFormatter DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public String formatNew(LocalDate date) {
return date.format(DATE_FORMAT); // 直接使用,线程安全
}
// 如果需要处理java.util.Date(兼容老代码)
public String formatNewFromDate(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate()
.format(DATE_FORMAT);
}
ThreadLocal家族速查表(2025更新版)
| 类型 | 适用场景 | JDK版本 | 注意事项 |
|---|---|---|---|
| ThreadLocal | 普通线程隔离 | 1.2+ | 必须手动remove |
| InheritableThreadLocal | 简单父子线程 | 1.2+ | 线程池失效 |
| TransmittableThreadLocal | 线程池场景 | 第三方 | 需要引入阿里TTL依赖 |
| ScopedValue | 只读上下文、虚拟线程 | 21+预览/23+正式 | 不可变,自动清理 |
总结
又是没有大厂约面日子😣😣😣,小编还在找实习的路上,这篇文章是我的笔记汇总整理。
参考资源
- JDK ThreadLocal JavaDoc
- JDK ScopedValue JavaDoc (JDK 21+)
- JEP 429: Scoped Values (Incubator)
- JEP 444: Virtual Threads
- 阿里巴巴 TransmittableThreadLocal (TTL)
java.lang.ThreadLocal- JDK源码java.lang.Thread- threadLocals字段java.lang.InheritableThreadLocal- 子线程继承- OWASP Session Management Cheat Sheet
- Doug Lea - 《Java Concurrency in Practice》
- Brian Goetz - Java内存模型相关论文