ThreadLocal 最佳实践
ThreadLocal 的 API 就那么几个,但工程中的用法却有门道。
用得好,它是传递上下文的利器;用不好,它是内存泄漏和诡异 bug 的根源。
经典使用场景
场景一:用户上下文传递
最常见的场景:HTTP 请求中的用户信息,需要在 Controller、Service、DAO 多层之间传递。
java
public class UserContextHolder {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public static User getCurrentUser() {
return currentUser.get();
}
public static void setUser(User user) {
currentUser.set(user);
}
// ⚠️ 用完必须清理
public static void clear() {
currentUser.remove();
}
}Filter 中设置,拦截器中清理:
java
@WebFilter
public class UserContextFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
try {
HttpServletRequest request = (HttpServletRequest) req;
User user = extractUser(request);
UserContextHolder.setUser(user); // 设置
chain.doFilter(req, res);
} finally {
UserContextHolder.clear(); // 清理
}
}
}场景二:数据库连接(手动事务)
不使用 Spring 声明式事务时,手动管理连接的经典模式:
java
public class TransactionManager {
private static final ThreadLocal<Connection> connection =
ThreadLocal.withInitial(() -> {
// 懒加载,从数据源获取连接
return DataSource.getConnection();
});
public static Connection getConnection() {
return connection.get();
}
public static void beginTransaction() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false);
}
public static void commit() throws SQLException {
getConnection().commit();
getConnection().setAutoCommit(true);
}
public static void rollback() throws SQLException {
getConnection().rollback();
getConnection().setAutoCommit(true);
}
public static void close() {
connection.remove();
}
}
// Service 层使用
public void transfer(String from, String to, double amount) {
try {
TransactionManager.beginTransaction();
// 转账操作
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
throw e;
} finally {
TransactionManager.close();
}
}场景三:链路追踪(TraceId)
每个请求分配一个唯一 ID,贯穿整个调用链:
java
public class TraceContext {
private static final ThreadLocal<String> traceId =
ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
private static final ThreadLocal<List<String>> logs =
ThreadLocal.withInitial(ArrayList::new);
public static String getTraceId() {
return traceId.get();
}
public static void addLog(String log) {
logs.get().add("[" + getTraceId() + "] " + log);
}
public static List<String> getLogs() {
return new ArrayList<>(logs.get());
}
public static void clear() {
traceId.remove();
logs.remove();
}
}场景四:安全上下文
Spring Security 的 SecurityContextHolder 底层就是 ThreadLocal:
java
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> context =
ThreadLocal.withInitial(SecurityContext::new);
public static SecurityContext getContext() {
return context.get();
}
public static void setContext(SecurityContext ctx) {
context.set(ctx);
}
public static void clear() {
context.remove();
}
public static void main(String[] args) {
// 模拟认证
SecurityContext ctx = getContext();
ctx.setAuthenticated(true);
ctx.setPrincipal("user1");
setContext(ctx);
// 任何层级都能获取
checkPermission("READ");
clear();
}
private static void checkPermission(String permission) {
SecurityContext ctx = getContext();
System.out.println("检查权限 " + permission + ": " +
ctx.isAuthenticated());
}
static class SecurityContext {
private boolean authenticated;
private String principal;
public boolean isAuthenticated() { return authenticated; }
public void setAuthenticated(boolean b) { this.authenticated = b; }
public String getPrincipal() { return principal; }
public void setPrincipal(String p) { this.principal = p; }
}
}场景五:方法耗时追踪
无侵入地追踪每个方法的执行时间:
java
public class TimeTracker {
private static final ThreadLocal<Long> startTime =
ThreadLocal.withInitial(System::currentTimeMillis);
public static void start() {
startTime.set(System.currentTimeMillis());
}
public static long elapsed() {
return System.currentTimeMillis() - startTime.get();
}
public static void clear() {
startTime.remove();
}
// AOP 风格的使用
public static <T> T execute(Supplier<T> action) {
start();
try {
return action.get();
} finally {
System.out.println("耗时: " + elapsed() + "ms");
clear();
}
}
}必须清理的场景
1. 线程池
线程复用,必须每次任务结束后清理:
java
// ❌ 错误
executor.submit(() -> {
context.set(value);
// 任务结束,context 没有被清理
});
// ✅ 正确
executor.submit(() -> {
try {
context.set(value);
// 业务逻辑
} finally {
context.remove(); // 必须清理
}
});2. 异步处理(CompletableFuture)
java
// ❌ 错误
CompletableFuture.supplyAsync(() -> {
return context.get(); // 主线程的值拿不到
});
// ✅ 正确:手动传递
CompletableFuture.supplyAsync(() -> {
String value = context.get(); // ITL 可以继承,ThreadLocal 拿不到
return process(value);
});3. 框架整合(Filter / Interceptor)
Web 框架中的标准做法:
java
// Filter(入口设置,出口清理)
public class ContextFilter {
private static final ThreadLocal<Context> ctx = new ThreadLocal<>();
public void doFilter(...) {
try {
ctx.set(buildContext());
chain.doFilter();
} finally {
ctx.remove();
}
}
}清理时机总览
| 场景 | 清理时机 |
|---|---|
| Web 请求 | Filter/Interceptor finally |
| 线程池任务 | Runnable finally |
| 定时任务 | Task finally |
| 单线程使用 | try-finally |
封装最佳实践
方式一:静态工具类(最常见)
java
public final class Context {
private Context() {}
private static final ThreadLocal<User> USER = new ThreadLocal<>();
private static final ThreadLocal<Trace> TRACE = new ThreadLocal<>();
public static User getUser() { return USER.get(); }
public static void setUser(User u) { USER.set(u); }
public static void clearUser() { USER.remove(); }
public static Trace getTrace() { return TRACE.get(); }
public static void setTrace(Trace t) { TRACE.set(t); }
public static void clearTrace() { TRACE.remove(); }
// 一键清理所有
public static void clearAll() {
USER.remove();
TRACE.remove();
}
}方式二:AutoCloseable(更优雅)
java
public class ThreadLocalHolder<T> implements AutoCloseable {
private final ThreadLocal<T> local = new ThreadLocal<>();
public void set(T value) {
local.set(value);
}
public T get() {
return local.get();
}
@Override
public void close() {
local.remove();
}
}
// 使用
try (ThreadLocalHolder<String> holder = new ThreadLocalHolder<>()) {
holder.set("value");
// 业务逻辑
} // 自动清理方式三:模板方法封装
java
public class ContextTemplate {
public static <T, R> R execute(Context ctx, Supplier<R> supplier) {
ContextHolder.set(ctx);
try {
return supplier.get();
} finally {
ContextHolder.clear();
}
}
public static <T> void execute(Context ctx, Runnable runnable) {
ContextHolder.set(ctx);
try {
runnable.run();
} finally {
ContextHolder.clear();
}
}
}常见错误汇总
| 错误 | 后果 | 解决 |
|---|---|---|
| set 后不 remove | 内存泄漏 | try-finally |
| 线程池任务不清理 | 数据污染 | 任务内 try-finally |
| 子线程需要继承 | 拿不到值 | InheritableThreadLocal / TTL |
| 多级 ThreadLocal 只清理一个 | 部分泄漏 | 统一清理入口 |
| 在 finally 中 remove 后又 set | 白清理 | 设计好执行顺序 |
选择口诀
单线程 → ThreadLocal
子线程要继承 → InheritableThreadLocal
线程池要继承 → TransmittableThreadLocal
Web 请求 → Filter 中统一清理要点回顾
- ThreadLocal 适合传递线程上下文,避免层层传参
- 用完必须 remove(),try-finally 是标准做法
- Web 应用在 Filter 中设置和清理
- 线程池场景必须每次任务后清理
- 封装成工具类,统一管理 set/get/remove
用好 ThreadLocal 的核心就一句话:入口 set,出口 remove,中间只 get。
相关链接
- ThreadLocal 核心概念 - 基本原理
- ThreadLocal 方法详解 - API 详解
- InheritableThreadLocal - 子线程继承
- ThreadLocal 内存泄漏 - 泄漏原因
- ThreadLocal 实现原理 - 内部机制
