Elegant Methods for Passing Data Between Parent and Child Threads in Spring Boot
This article explains four techniques—manual setting, TaskDecorator, InheritableThreadLocal, and TransmittableThreadLocal—for propagating context such as user information from a parent thread to child threads in Spring Boot asynchronous execution, providing code examples and recommendations.
Hello everyone, I am Chen. In Spring Boot asynchronous development, we often need to transfer data like user information or trace IDs between parent and child threads.
For example, user login information can be stored in a ThreadLocal to ensure thread isolation:
/**
* @author 公众号:码猿技术专栏
* @description 用户上下文信息
*/
public class OauthContext {
private static final ThreadLocal
loginValThreadLocal = new ThreadLocal<>();
public static LoginVal get() {
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal) {
loginValThreadLocal.set(loginVal);
}
public static void clear() {
loginValThreadLocal.remove();
}
}How can a child thread obtain this LoginVal ?
Below are several elegant ways to achieve data transmission between parent and child threads in Spring Boot.
1. Manual Setting
Each asynchronous task requires two steps: obtain the parent thread's LoginVal and set it into the child thread.
public void handlerAsync() {
// 1. Get the parent thread's loginVal
LoginVal loginVal = OauthContext.get();
log.info("Parent thread value: {}", OauthContext.get());
CompletableFuture.runAsync(() -> {
// 2. Set the child thread's value
OauthContext.set(loginVal);
log.info("Child thread value: {}", OauthContext.get());
});
}Although this works, the repetitive code is cumbersome.
2. ThreadPool TaskDecorator
TaskDecorator is an interface that allows you to decorate a task before execution, mainly used for context propagation or monitoring.
Implement the decorator as follows:
/**
* @author 公众号:码猿技术专栏
* @description 上下文装饰器
*/
public class ContextTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Capture the parent thread's loginVal
LoginVal loginVal = OauthContext.get();
return () -> {
try {
// Set the captured value into the child thread
OauthContext.set(loginVal);
runnable.run();
} finally {
// Clear to avoid memory leaks
OauthContext.clear();
}
};
}
}Configure a ThreadPoolTaskExecutor to use the decorator:
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
poolTaskExecutor.setCorePoolSize(xx);
poolTaskExecutor.setMaxPoolSize(xx);
// Set keep-alive seconds
poolTaskExecutor.setKeepAliveSeconds(xx);
// Set queue capacity
poolTaskExecutor.setQueueCapacity(xx);
// Apply the TaskDecorator for context propagation
poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// Wait for tasks to complete on shutdown
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
return poolTaskExecutor;
}Now business code no longer needs to manually set the context:
public void handlerAsync() {
log.info("Parent thread user info: {}", OauthContext.get());
// Execute async task with the specified thread pool
CompletableFuture.runAsync(() ->
log.info("Child thread user info: {}", OauthContext.get()), taskExecutor);
}The result shows that both parent and child threads share the same user information.
Note: Regardless of the method used, you must specify a thread pool.
3. InheritableThreadLocal
This approach is not recommended because InheritableThreadLocal can cause issues when threads are pooled.
Replace ThreadLocal with InheritableThreadLocal :
public class OauthContext {
private static final InheritableThreadLocal
loginValThreadLocal = new InheritableThreadLocal<>();
public static LoginVal get() {
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal) {
loginValThreadLocal.set(loginVal);
}
public static void clear() {
loginValThreadLocal.remove();
}
}4. TransmittableThreadLocal
TransmittableThreadLocal (TTL) is an Alibaba open‑source tool that extends InheritableThreadLocal to work correctly with thread pools.
Add the Maven dependency:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>Modify OauthContext to use TTL:
public class OauthContext {
private static final TransmittableThreadLocal
loginValThreadLocal = new TransmittableThreadLocal<>();
public static LoginVal get() {
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal) {
loginValThreadLocal.set(loginVal);
}
public static void clear() {
loginValThreadLocal.remove();
}
}The core of TTL involves capturing the current thread's values, replaying them in the worker thread, and restoring the original values after execution. Key classes include TransmittableThreadLocal , Transmitter , TtlCallable , TtlRunnable , and the wrapper utilities TtlExecutors and ExecutorServiceTtlWrapper . Below is a simplified excerpt of the implementation:
public class TransmittableThreadLocal
extends InheritableThreadLocal
implements TtlCopier
{ ... }
public class Transmitter {
public static Object capture() { ... }
public static Object replay(Object captured) { ... }
public static void restore(Object backup) { ... }
}
public class TtlCallable
implements Callable
{
private final Callable
callable;
private final AtomicReference
capturedRef;
@Override
public V call() throws Exception {
Object captured = capturedRef.get();
Object backup = replay(captured);
try {
return callable.call();
} finally {
restore(backup);
}
}
}In practice, you wrap tasks with TtlCallable.get(task) or TtlRunnable.get(runnable) via TtlExecutors , and the framework handles context propagation automatically.
Summary
The article presented four solutions for parent‑child thread data transmission. The author recommends using the TaskDecorator approach (method 2) or the TransmittableThreadLocal approach (method 4) for production environments.
Finally, the author asks readers to like, view, share, and collect the article if they found it helpful, and promotes a paid knowledge community offering advanced Spring, MyBatis, RocketMQ, and micro‑service practice courses.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.