4 Elegant Ways to Transfer Data Between Async Threads in Spring Boot
The article compares four techniques—manual ThreadLocal copying, TaskDecorator, InheritableThreadLocal, and Alibaba's TransmittableThreadLocal—for passing user context and other data from parent to child threads in Spring Boot async execution, recommending the TaskDecorator and TransmittableThreadLocal approaches for production use.
1. Manual Setting
Each asynchronous task must perform two steps: retrieve the parent thread's LoginVal from OauthContext and set it into the child thread before execution. Example:
public void handlerAsync() {
// 1. Get parent thread's loginVal
LoginVal loginVal = OauthContext.get();
log.info("父线程的值:{}", OauthContext.get());
CompletableFuture.runAsync(() -> {
// 2. Set child thread's value, reuse
OauthContext.set(loginVal);
log.info("子线程的值:{}", OauthContext.get());
});
}While functional, this approach requires repetitive boiler‑plate code for every async call.
2. ThreadPool TaskDecorator
TaskDecoratoris an official Spring API that decorates task execution, allowing context propagation or monitoring. Implement it as follows:
/**
* @description 上下文装饰器
*/
public class ContextTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Capture parent thread's loginVal
LoginVal loginVal = OauthContext.get();
return () -> {
try {
// Transfer parent request info to child thread
OauthContext.set(loginVal);
// Execute child thread logic
runnable.run();
} finally {
// Clear to avoid memory leaks
OauthContext.clear();
}
};
}
}Configure the thread pool to use this decorator:
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
poolTaskExecutor.setCorePoolSize(xx);
poolTaskExecutor.setMaxPoolSize(xx);
poolTaskExecutor.setKeepAliveSeconds(xx);
poolTaskExecutor.setQueueCapacity(xx);
poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
return poolTaskExecutor;
}Business code no longer needs to set the child thread's context manually:
public void handlerAsync() {
log.info("父线程的用户信息:{}", OauthContext.get());
// Execute async task with the configured pool
CompletableFuture.runAsync(() ->
log.info("子线程的用户信息:{}", OauthContext.get()), taskExecutor);
}Result screenshot (omitted). The same mechanism works with @Async annotation.
Note: Regardless of the method used, a thread pool must be specified.
3. InheritableThreadLocal
Replacing ThreadLocal with InheritableThreadLocal enables parent‑child value inheritance, but it suffers from reuse problems when used with thread pools, so it is not recommended. Example:
public class OauthContext {
private static final InheritableThreadLocal<LoginVal> 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
Alibaba's open‑source TransmittableThreadLocal (TTL) extends InheritableThreadLocal and implements TtlCopier to correctly propagate ThreadLocal values across pooled threads. Add the dependency:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>Modify the context class:
public class OauthContext {
private static final TransmittableThreadLocal<LoginVal> 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(); }
}TTL works by capturing the current thread's values, wrapping tasks, and restoring the original values after execution. Core classes include TransmittableThreadLocal, TtlCallable, TtlRunnable, and utility Transmitter. The capture‑replay‑restore flow is roughly:
public class Transmitter {
public static Object capture() { return new Snapshot(captureTtlValues(), captureThreadLocalValues()); }
public static Object replay(Object captured) { /* replace child thread values */ }
public static void restore(Object backup) { /* restore original values */ }
}When a task is submitted via ExecutorService, TTL wraps it with TtlCallable (or TtlRunnable) which calls capture() in the parent, replaces the child thread's context in call(), executes the original callable, then restores the previous context.
Thus, using TTL ensures that any ThreadLocal (including those registered with TTL) is safely transmitted to pooled threads without the memory‑leak risks of InheritableThreadLocal.
Conclusion
The article lists four solutions and recommends approaches 2 (TaskDecorator) and 4 (TransmittableThreadLocal) as the most practical for real‑world Spring Boot asynchronous development.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
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.
