SpringBoot實現(xiàn)ThreadLocal父子線程傳值的幾種方式
前言
在日常開發(fā)中,我們經常會遇到需要在父子線程之間傳遞數(shù)據(jù)的場景。比如用戶身份信息、請求ID、鏈路追蹤ID等。
ThreadLocal作為Java中重要的線程本地變量機制,為我們提供了在單個線程內存儲數(shù)據(jù)的便利。但是,當涉及到父子線程之間的數(shù)據(jù)傳遞時,ThreadLocal默認的行為并不能滿足我們的需求。
本文將介紹在SpringBoot應用中實現(xiàn)ThreadLocal父子線程傳值的幾種方式。
ThreadLocal 基礎回顧
首先,讓我們簡單回顧一下ThreadLocal的基本原理:
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,從而實現(xiàn)線程隔離。
問題的提出
讓我們通過一個簡單的例子來看看ThreadLocal在父子線程中的默認行為
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來解決父子線程傳值的問題
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的實現(xiàn)在Thread類中
public class Thread implements Runnable {
// 父線程的inheritableThreadLocals會在創(chuàng)建子線程時復制
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. 時機限制:只在創(chuàng)建線程時進行值傳遞,后續(xù)修改不會傳遞到已創(chuàng)建的子線程
- 2. 線程池問題:在線程池中使用時,由于線程會被復用,可能導致數(shù)據(jù)混亂
解決方案二:使用TransmittableThreadLocal
阿里巴巴開源的TransmittableThreadLocal(TTL)是解決線程池場景下父子線程傳值問題的優(yōu)秀方案:
添加依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
基本使用
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異步執(zhí)行器
@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的請求攔截器
@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任務進行裝飾
@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來傳遞請求上下文
@Service
public class ContextService {
@Async
public CompletableFuture<String> asyncMethod() {
// 獲取父線程的請求上下文
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return CompletableFuture.supplyAsync(() -> {
// 在異步線程中設置上下文
RequestContextHolder.setRequestAttributes(attributes);
try {
// 執(zhí)行業(yè)務邏輯
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:
public class SafeThreadLocalUsage {
private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public void doWork() {
try {
threadLocal.set(someValue);
// 業(yè)務邏輯
} finally {
threadLocal.remove(); // 防止內存泄漏
}
}
}
2. 上下文封裝
建議封裝一個統(tǒng)一的上下文管理器:
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. 攔截器統(tǒng)一管理
@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中實現(xiàn)ThreadLocal父子線程傳值的方案:
- 1. InheritableThreadLocal:最簡單的原生解決方案,適用于基礎場景
- 2. TransmittableThreadLocal:功能強大的第三方解決方案,特別適合線程池場景
- 3. TaskDecorator:Spring提供的優(yōu)雅解決方案,集成度高
- 4. RequestContextHolder:Spring Web應用的內置方案
在實際項目中,我們應該根據(jù)具體的業(yè)務場景和技術棧選擇合適的方案。
同時,要注意內存管理和上下文清理,確保系統(tǒng)的穩(wěn)定性和性能。
以上就是SpringBoot實現(xiàn)ThreadLocal父子線程傳值的幾種方式的詳細內容,更多關于SpringBoot ThreadLocal父子線程傳值的資料請關注腳本之家其它相關文章!
相關文章
Java中的System類、BigInteger類和BigDecimal類詳解
這篇文章主要介紹了Java中的System類、BigInteger類和BigDecimal類詳解,arraycopy()方法,復制數(shù)組元素,比較適合底層調用,一般使用Arrays.copyOf()完成復制數(shù)組,需要的朋友可以參考下2023-09-09
最新Spring Security實戰(zhàn)教程之Spring Security安全框架指南
SpringSecurity是Spring生態(tài)系統(tǒng)中的核心組件,提供認證、授權和防護機制,以保護應用免受各種安全威脅,它支持多種認證方式,并通過攔截器和過濾器鏈進行安全檢查,本文通過搭建SpringBoot+SpringSecurity項目,幫助如何快速上手并應用SpringSecurity,感興趣的朋友一起看看吧2025-03-03
Spring?MVC啟動之HandlerMapping作用及實現(xiàn)詳解
這篇文章主要為大家介紹了Spring?MVC啟動之HandlerMapping作用及實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03

