前言
在日常开发中,我们经常会遇到需要在父子线程之间传递数据的场景。比如用户身份信息、请求ID、链路追踪ID等。
ThreadLocal作为Java中重要的线程本地变量机制,为我们提供了在单个线程内存储数据的便利。但是,当涉及到父子线程之间的数据传递时,ThreadLocal默认的行为并不能满足我们的需求。
本文将介绍在SpringBoot应用中实现ThreadLocal父子线程传值的几种方式。
ThreadLocal 基础回顾
首先,让我们简单回顾一下ThreadLocal的基本原理:
java
public class ThreadLocal<T> {
// 每个线程都有自己独立的ThreadLocalMap
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
}
ThreadLocal的核心思想是为每个线程维护一个独立的ThreadLocalMap,从而实现线程隔离。
问题的提出
让我们通过一个简单的例子来看看ThreadLocal在父子线程中的默认行为
java
public class ThreadLocalDemo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("主线程的值");
System.out.println("主线程获取: " + threadLocal.get()); // 输出: 主线程的值
Thread childThread = new Thread(() -> {
System.out.println("子线程获取: " + threadLocal.get()); // 输出: null
});
childThread.start();
}
}
从上面的例子可以看出,子线程无法访问父线程中设置的ThreadLocal值。这是因为每个线程都有自己独立的ThreadLocalMap。
解决方案一:InheritableThreadLocal
Java为我们提供了InheritableThreadLocal来解决父子线程传值的问题
java
public class InheritableThreadLocalDemo {
private static InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("主线程的值");
System.out.println("主线程获取: " + inheritableThreadLocal.get());
Thread childThread = new Thread(() -> {
System.out.println("子线程获取: " + inheritableThreadLocal.get()); // 输出: 主线程的值
});
childThread.start();
}
}
InheritableThreadLocal 原理分析
InheritableThreadLocal的实现在Thread类中
java
public class Thread implements Runnable {
// 父线程的inheritableThreadLocals会在创建子线程时复制
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ...
}
}
InheritableThreadLocal 的局限性
- 1. 时机限制:只在创建线程时进行值传递,后续修改不会传递到已创建的子线程
- 2. 线程池问题:在线程池中使用时,由于线程会被复用,可能导致数据混乱
解决方案二:使用TransmittableThreadLocal
阿里巴巴开源的TransmittableThreadLocal(TTL)是解决线程池场景下父子线程传值问题的优秀方案:
添加依赖
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
基本使用
java
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
public class TtlDemo {
private static TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
public static void main(String[] args) {
ttl.set("主线程的值");
System.out.println("主线程获取: " + ttl.get());
// 使用TTL装饰的线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(2)
);
executor.submit(() -> {
System.out.println("线程池任务1获取: " + ttl.get()); // 输出: 主线程的值
});
// 修改值后提交新任务
ttl.set("更新后的值");
executor.submit(() -> {
System.out.println("线程池任务2获取: " + ttl.get()); // 输出: 更新后的值
});
}
}
TTL 与Spring Boot的集成
在Spring Boot应用中,我们可以通过以下方式集成TTL
1. 配置TTL异步执行器
java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
// 使用TTL装饰
return TtlExecutors.getTtlExecutor(executor);
}
}
2. 使用TTL的请求拦截器
java
@Component
public class TtlRequestInterceptor implements HandlerInterceptor {
private static final TransmittableThreadLocal<String> requestId =
new TransmittableThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
String id = UUID.randomUUID().toString();
requestId.set(id);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
requestId.remove(); // 清理
}
public static String getRequestId() {
return requestId.get();
}
}
解决方案三:自定义TaskDecorator
Spring提供了TaskDecorator接口,允许我们对Runnable任务进行装饰
java
@Configuration
@EnableAsync
public class CustomAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("CustomAsync-");
// 设置自定义装饰器
executor.setTaskDecorator(new ContextCopyingDecorator());
executor.initialize();
return executor;
}
private static class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 获取父线程的ThreadLocal上下文
Map<String, Object> context = getContextFromCurrentThread();
return () -> {
try {
// 在子线程中复制上下文
setContextToCurrentThread(context);
runnable.run();
} finally {
clearCurrentThreadContext();
}
};
}
private Map<String, Object> getContextFromCurrentThread() {
Map<String, Object> context = new HashMap<>();
// 收集当前线程的ThreadLocal值
// 例如:从自定义的ThreadLocal中获取
if (UserContext.getUser() != null) {
context.put("userInfo", UserContext.getUser());
}
if (RequestContextHolder.getRequestAttributes() != null) {
context.put("requestAttributes", RequestContextHolder.getRequestAttributes());
}
return context;
}
private void setContextToCurrentThread(Map<String, Object> context) {
// 在子线程中设置上下文
if (context.containsKey("userInfo")) {
UserContext.setUser((UserContext.UserInfo) context.get("userInfo"));
}
if (context.containsKey("requestAttributes")) {
RequestContextHolder.setRequestAttributes((RequestAttributes) context.get("requestAttributes"));
}
}
private void clearCurrentThreadContext() {
// 清理上下文,防止内存泄漏
UserContext.clear();
RequestContextHolder.resetRequestAttributes();
}
}
}
解决方案四:使用Spring的RequestContextHolder
在Spring Web应用中,我们可以利用RequestContextHolder来传递请求上下文
java
@Service
public class ContextService {
@Async
public CompletableFuture<String> asyncMethod() {
// 获取父线程的请求上下文
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return CompletableFuture.supplyAsync(() -> {
// 在异步线程中设置上下文
RequestContextHolder.setRequestAttributes(attributes);
try {
// 执行业务逻辑
String result = doSomeWork();
return result;
} finally {
RequestContextHolder.resetRequestAttributes();
}
});
}
private String doSomeWork() {
// 获取请求相关的信息
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return "处理完成: " + request.getRequestURI();
}
}
方案选择建议
基于上述性能对比和适用场景,我们可以给出以下选择建议:
- 1. 简单的父子线程传值:使用InheritableThreadLocal
- 2. 线程池场景:推荐使用TransmittableThreadLocal
- 3. Spring异步任务:使用TaskDecorator
- 4. Web应用请求上下文:使用RequestContextHolder
最佳实践
1. 内存泄漏预防
无论使用哪种方案,都要注意及时清理ThreadLocal:
java
public class SafeThreadLocalUsage {
private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public void doWork() {
try {
threadLocal.set(someValue);
// 业务逻辑
} finally {
threadLocal.remove(); // 防止内存泄漏
}
}
}
2. 上下文封装
建议封装一个统一的上下文管理器:
java
public class UserContext {
private static final ThreadLocal<UserInfo> USER_CONTEXT =
new TransmittableThreadLocal<>();
public static void setUser(UserInfo user) {
USER_CONTEXT.set(user);
}
public static UserInfo getUser() {
return USER_CONTEXT.get();
}
public static void clear() {
USER_CONTEXT.remove();
}
public static class UserInfo {
private String userId;
private String userName;
private String requestId;
// getters and setters
}
}
3. 拦截器统一管理
java
@Component
public class UserContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
UserInfo userInfo = buildUserInfo(request);
UserContext.setUser(userInfo);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
UserContext.clear();
}
private UserInfo buildUserInfo(HttpServletRequest request) {
UserInfo userInfo = new UserInfo();
userInfo.setUserId(request.getHeader("X-User-Id"));
userInfo.setRequestId(UUID.randomUUID().toString());
return userInfo;
}
}
总结
本文介绍了四种在SpringBoot中实现ThreadLocal父子线程传值的方案:
- 1. InheritableThreadLocal:最简单的原生解决方案,适用于基础场景
- 2. TransmittableThreadLocal:功能强大的第三方解决方案,特别适合线程池场景
- 3. TaskDecorator:Spring提供的优雅解决方案,集成度高
- 4. RequestContextHolder:Spring Web应用的内置方案
在实际项目中,我们应该根据具体的业务场景和技术栈选择合适的方案。
同时,要注意内存管理和上下文清理,确保系统的稳定性和性能。