1.TransmittableThreadLocal簡介
com.alibaba.ttl.TransmittableThreadLocal
(簡稱 TTL)是阿里巴巴開源的一個工具類,旨在解決?ThreadLocal
?在線程池中無法傳遞上下文變量?的問題。它是對?InheritableThreadLocal
?的增強,尤其適用于異步編程、線程池任務調度等場景。
1.1 核心功能
-
上下文傳遞:在普通線程池中,
ThreadLocal
?的值不會自動傳遞到新線程。TransmittableThreadLocal
?通過?任務包裝機制,將主線程的上下文變量傳遞到子線程(如線程池中的線程)中。 -
兼容性:它兼容?
ThreadLocal
?和?InheritableThreadLocal
?的行為,同時支持 Java 8+ 的異步編程模型(如?CompletableFuture
、ForkJoinPool
?等)。 -
生命周期管理:支持在任務執行前后自動綁定/解綁上下文,避免內存泄漏。
1.2 典型使用場景
- 分布式追蹤(如 SkyWalking、Zipkin):在異步調用鏈中傳遞 Trace ID。
- 日志上下文(如 MDC):在異步任務中保留請求標識(如用戶 ID、請求 ID)。
- 事務傳播:在線程池中傳遞事務上下文(如 Seata 分布式事務)。
- 權限驗證:在異步任務中傳遞用戶身份信息。
2.TransmittableThreadLocal基本用法
step1.引入依賴
<!-- Maven -->
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.12.1</version> <!-- 使用最新版本 -->
</dependency>
step2. 定義 TransmittableThreadLocal
import com.alibaba.ttl.TransmittableThreadLocal;public class Context {private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();public static void set(String value) {CONTEXT.set(value);}public static String get() {return CONTEXT.get();}
}
step3. 各種用法
import com.alibaba.ttl.TtlRunnable;ExecutorService executor = Executors.newFixedThreadPool(2);
// 主線程設置上下文
Context.set("Hello TTL");// 提交任務前包裝 Runnable
// TtlRunnable 是 Runnable 的任務包裝器
executor.submit(TtlRunnable.get(() -> {System.out.println("子線程: " + Context.get());
}, true)); // 第二個參數表示是否復制上下文//使用 TtlCallable 包裝 Future 任務
// TtlCallable 是 Callable 的任務包裝器
Future<String> future = executor.submit(TtlCallable.get(() -> {return "Result with context: " + Context.get();
}));//使用于 CompletableFuture
CompletableFuture.runAsync(TtlRunnable.get(() -> {System.out.println("CompletableFuture: " + Context.get());
}, true), executor);//如果使用自定義線程池,建議通過 TtlExecutors 包裝:
Executor ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));
關鍵特性
1. 自動傳播:如果線程池本身是 TTL 兼容的(如使用?TtlExecutors
?包裝),則無需手動包裝任務。
2. 線程池改造:如果使用自定義線程池,建議通過?TtlExecutors
?包裝:
ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
3. 清除機制:需要顯式調用?remove()
?避免內存泄漏:
try {Context.set("value");// 執行任務
} finally {Context.remove();
}
3. TransmittableThreadLocal實現上下文傳遞-完整demo
3.1 場景說明:
有一個“查詢用戶成績”場景,需要將用戶上下文(如用戶ID、租戶信息、AccessKey、Locale)在 Controller、Service 層甚至異步調用中傳遞。
3.2 實現目標
- 用戶請求進入系統后,從請求頭中提取上下文信息。
- 上下文信息在整個請求鏈路中自動傳遞,包括異步調用。
- 上下文信息不會被多個請求交叉污染。
- 上下文信息在請求結束后自動清理。
3.3 代碼
step1.?定義用戶上下文(UserContext)
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.Builder;
import lombok.Data;@Data
@Builder
public class UserContext {private static final TransmittableThreadLocal<UserContext> USER_CONTEXT= new TransmittableThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};/*** 用戶ID*/private String userId;/*** 租戶ID*/private String tenantId;/*** 訪問密鑰*/private String accessKey;/*** 語言*/private String locale;public UserContext() {}public UserContext(String userId, String tenantId, String accessKey, String locale) {this.userId = userId;this.tenantId = tenantId;this.accessKey = accessKey;this.locale = locale;}public static UserContext get() {return USER_CONTEXT.get();}public static void set(UserContext userContext) {USER_CONTEXT.set(userContext);}public static void remove() {USER_CONTEXT.remove();}}
step2.?配置攔截器(注入上下文UserContext)
定義一個UserContextInterceptor攔截器,用于從request Header中獲取用戶信息:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;public class UserContextInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 從請求頭中提取用戶信息(示例)String userId = request.getHeader("Custom-User-Id");String tenantId = request.getHeader("Custom-Tenant-Id");String accessKey = request.getHeader("Custom-Access-Key");String locale = request.getHeader("Custom-Accept-Language");// 設置用戶信息UserContext userContext = UserContext.builder().userId(userId).tenantId(tenantId).accessKey(accessKey).locale(locale).build();UserContext.set(userContext);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {// 務必在請求結束時清理上下文,避免內存泄漏UserContext.remove();}
}
注冊UserContextInterceptor攔截器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class UserContextWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserContextInterceptor()).addPathPatterns("/**");}
}
step3.在 Service 層使用上下文(UserContext)
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.stereotype.Service;@Service
public class ScoreService {//配置異步線程池(使用 TtlExecutors),用于測試多線程場景下,子線程也能獲取到主線程的上下文信息private Executor taskExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));public void queryScore() {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("Sync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Sync: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);}//多線程場景下,子線程也能獲取到主線程的上下文信息public void queryScoreAsync() {taskExecutor.execute(() -> {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("ASync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Async: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);});}
}
step4. Controller層
@RestController
public class ScoreController {@Autowiredprivate ScoreService scoreService;@GetMapping("ScoreController/queryScore")public String queryScore() {scoreService.queryScore();scoreService.queryScoreAsync();return "ScoreController queryScore success!";}@GetMapping("ScoreController/hello")public String hello() {return "ScoreController say hello";}
}
step5.測試
啟動應用后,在postman中做測試。
case1: 不設置Header,UserContext獲取的值為null:
case2: 設置Header,UserContext獲取的值為設置的值:
case3:使用?ThreadLocal 做對比測試,新建的子線程中無法獲取到UserContext設置的值
將UserContext.USER_CONTEXT設置為?ThreadLocal:
@Data
@Builder
public class UserContext {//private static final TransmittableThreadLocal<UserContext> USER_CONTEXT// = new TransmittableThreadLocal<UserContext>(){// @Override// protected UserContext initialValue() {// return new UserContext();// }//};// 改用 ThreadLocal,其他代碼不變private static final ThreadLocal<UserContext> USER_CONTEXT = new ThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};
}
設置Header后,發送請求,新建的子線程中無法獲取到UserContext設置的值: