TransmittableThreadLocal實現上下文傳遞-筆記

1.TransmittableThreadLocal簡介

com.alibaba.ttl.TransmittableThreadLocal(簡稱 TTL)是阿里巴巴開源的一個工具類,旨在解決?ThreadLocal?在線程池中無法傳遞上下文變量?的問題。它是對?InheritableThreadLocal?的增強,尤其適用于異步編程、線程池任務調度等場景。


1.1 核心功能

  1. 上下文傳遞:在普通線程池中,ThreadLocal?的值不會自動傳遞到新線程。TransmittableThreadLocal?通過?任務包裝機制,將主線程的上下文變量傳遞到子線程(如線程池中的線程)中。

  2. 兼容性:它兼容?ThreadLocal?和?InheritableThreadLocal?的行為,同時支持 Java 8+ 的異步編程模型(如?CompletableFutureForkJoinPool?等)。

  3. 生命周期管理:支持在任務執行前后自動綁定/解綁上下文,避免內存泄漏。


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設置的值:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/80645.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80645.shtml
英文地址,請注明出處:http://en.pswp.cn/web/80645.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TDengine 安全部署配置建議

背景 TDengine 的分布式、多組件特性導致 TDengine 的安全配置是生產系統中比較關注的問題。本文檔旨在對 TDengine 各組件及在不同部署方式下的安全問題進行說明&#xff0c;并提供部署和配置建議&#xff0c;為用戶的數據安全提供支持。 安全配置涉及組件 TDengine 包含多…

在Cursor中啟用WebStorm/IntelliJ風格快捷鍵

在Cursor中啟用WebStorm/IntelliJ風格快捷鍵 方法一&#xff1a;使用預置快捷鍵方案 打開快捷鍵設置 Windows/Linux: Ctrl K → Ctrl SmacOS: ? K → ? S 搜索預設方案 在搜索框中輸入keyboard shortcuts&#xff0c;選擇Preferences: Open Keyboard Shortcuts (JSON) …

python打卡day30@浙大疏錦行

知識點回顧&#xff1a; 導入官方庫的三種手段導入自定義庫/模塊的方式導入庫/模塊的核心邏輯&#xff1a;找到根目錄&#xff08;python解釋器的目錄和終端的目錄不一致&#xff09; 作業&#xff1a;自己新建幾個不同路徑文件嘗試下如何導入 具體操作步驟&#xff1a; 在桌面…

【kafka】基本命令

創建 Kafka Topic 的命令 以下是創建 Kafka Topic 的幾種常用方法&#xff1a; 1. 使用 kafka-topics.sh 基礎命令&#xff08;Kafka 自帶工具&#xff09; bin/kafka-topics.sh --create \--bootstrap-server <broker地址:端口> \--topic <topic名稱> \--parti…

編程速遞:適用于 Delphi 12.3 的 FMX Linux 現已推出

Embarcadero非常高興地宣布&#xff0c;用于使用Delphi構建Linux客戶端應用程序的FMX Linux UI庫再次在RAD Studio 12.3版本以及RAD Studio 12.2版本中提供支持&#xff0c;同時也適用于更早的版本。 作為RAD Studio的一個附加庫&#xff0c;FMX Linux為開發面向Linux的圖形用…

通過實例講解螺旋模型

目錄 一、螺旋模型的核心概念 二、螺旋模型在電子商城系統開發中的應用示例 第 1 次螺旋:項目啟動與風險初探

vue3 vite 路由

如路由是這種格式 http://localhost:7058/admin/product/brand路由配置如下 import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import NProgress from nprogress; import nprogress/nprogress.css; import {errorRour…

【Redis】Hash 存儲相比 String 存儲的優勢

在 Redis 中&#xff0c;Hash 存儲相比 String 存儲具有以下 優勢&#xff0c;特別適用于某些特定場景&#xff1a; ? 1. 更節省內存&#xff08;尤其適合存儲對象&#xff09; Hash 內部使用壓縮列表&#xff08;ziplist&#xff09;或哈希表實現&#xff0c;在數據量較小時…

CSS詳解:特性、選擇器與優先級

CSS詳解&#xff1a;特性、選擇器與優先級 目錄 CSS詳解&#xff1a;特性、選擇器與優先級一、CSS的核心特性1. 層疊性&#xff08;Cascading&#xff09;2. 繼承性&#xff08;Inheritance&#xff09;3. 優先級&#xff08;Specificity&#xff09;4. 響應式設計5. 動畫與過渡…

《算法導論(第4版)》閱讀筆記:p86-p90

《算法導論(第4版)》學習第 19 天&#xff0c;p83-p85 總結&#xff0c;總計 3 頁。 一、技術總結 無。 二、英語總結(生詞&#xff1a;2) 1. inkling (1)inkling: inclen(“utter in an undertone&#xff0c;低聲說話”) c. a hint(提示)&#xff1b;a slight knowledg…

nginx概念及使用

一、Nginx 核心概念 Nginx&#xff08;發音為 "engine-x"&#xff09;是一個高性能、開源的 Web 服務器和反向代理服務器&#xff0c;由俄羅斯工程師伊戈爾?賽索耶夫&#xff08;Igor Sysoev&#xff09;于 2004 年開發&#xff0c;最初用于解決當時高并發場景下 Ap…

2025藍橋杯JAVA編程題練習Day8

1. 路徑 題目描述 小藍學習了最短路徑之后特別高興&#xff0c;他定義了一個特別的圖&#xff0c;希望找到圖 中的最短路徑。 小藍的圖由 2021 個結點組成&#xff0c;依次編號 1 至 2021。 對于兩個不同的結點 a, b&#xff0c;如果 a 和 b 的差的絕對值大于 21&#xff0…

【趙渝強老師】Memcached的路由算法

Memcached支持兩種不同方式的客戶端路由算法&#xff0c;即&#xff1a;求余數Hash算法和一致性Hash算法。下面分別進行介紹。 一、 求余數的路由算法 求余數Hash算法的客戶端路由是對插入數據的鍵進行求余數&#xff0c;根據余數來決定存儲到哪個Memcached實例。 視頻講解如…

NLP學習路線圖(一): 線性代數(矩陣運算、特征值分解等)

引言&#xff1a;語言與矩陣的奇妙邂逅 在自然語言處理&#xff08;NLP&#xff09;的魔法世界里&#xff0c;每個詞語都像被施了變形術的精靈&#xff0c;在數學的殿堂中翩翩起舞。當我們用"king - man woman queen"這樣的向量魔法破解語義密碼時&#xff0c;線性…

BUUCTF PWN刷題筆記(持續更新!!)

ciscn_2019_c_1 64位&#xff0c;沒有開啟保護。點進去沒發現明顯的漏洞函數&#xff0c;考慮泄露libc基地址的rop構造。先看看有多少gadget 估計也夠用了。puts函數只接受一個參數&#xff0c;觀看匯編看看用的哪個寄存器傳輸的參數。 用的是edi。但是我們怎么找到so的版本呢…

Java EE初階——線程安全

1. 線程的狀態 1. 線程狀態分類&#xff08;Thread.State 枚舉&#xff09; Java 定義了 6 種線程狀態&#xff0c;這些狀態均由 java.lang.Thread.State 枚舉表示&#xff1a; NEW&#xff08;新建&#xff09; 線程對象已創建&#xff0c;但尚未調用 start() 方法。此時線程…

Vue 3.0中響應式依賴和更新

響應式依賴和更新是Vue 3.0中最重要的機制&#xff0c;其核心代碼如下&#xff0c;本文將結合代碼對這個設計機制作出一些解釋。 // 全局依賴存儲&#xff1a;WeakMap<target, Map<key, Set<effect>>> const targetMap new WeakMap();// 當前活動的副作用函…

一、內存調優

一、內存調優 什么是內存泄漏 監控Java內存的常用工具 內存泄露的常見場景 內存泄露的解決方案 內存泄露與內存溢出的區別 內存泄露&#xff1a;在Java中如果不再使用一個對象&#xff0c;但是該對象依然在GC ROOT的引用鏈上&#xff0c;這個對象就不會被垃圾回收器回收&…

Linux /etc/rc.d/init.d/

在傳統的 SysV init 系統中&#xff0c;服務啟動腳本通常位于 /etc/rc.d/init.d/ 目錄下。這些腳本可以直接執行以啟動、停止或重啟服務&#xff0c;并且可以接受參數如 start, stop, status 等。 如果你想知道位于 /etc/rc.d/init.d/ 目錄下的某個腳本文件實際上指向哪里,如果…

S7 200 smart連接Profinet轉ModbusTCP網關與西門子1200PLC配置案例

控制要求&#xff1a;使用MODBUSTCP通信進行兩臺PLC之間的數據交換&#xff0c;由于改造現場不能改動程序&#xff0c;只留出了對應的IQ地址。于是客戶決定使用網關進行通訊把數據傳到plc。 1、讀取服務器端40001~40005地址中的數據&#xff0c;放入到VW200~VW208中&#xff1…