深入理解TransmittableThreadLocal:原理、使用與避坑指南

?一、ThreadLocal與InheritableThreadLocal回顧

在介紹TransmittableThreadLocal之前,我們先回顧一下Java中的ThreadLocal和InheritableThreadLocal。

1. ThreadLocal

ThreadLocal提供了線程局部變量,每個線程都可以通過get/set訪問自己獨立的變量副本。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("main thread value");new Thread(() -> {System.out.println(threadLocal.get()); // 輸出null
}).start();
2. InheritableThreadLocal

InheritableThreadLocal可以解決父子線程間值傳遞的問題:```java

InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("main thread value");new Thread(() -> {System.out.println(inheritableThreadLocal.get()); // 輸出"main thread value"
}).start();

但是InheritableThreadLocal有局限性:
- 只支持創建新線程時的值傳遞
- 線程池場景下不適用(線程復用)

?二、TransmittableThreadLocal介紹

TransmittableThreadLocal(TTL)是阿里開源的一個線程間數據傳遞解決方案,解決了InheritableThreadLocal在線程池場景下的問題。

核心特性
- 支持線程池場景下的值傳遞
- 支持任務執行前的自定義邏輯
- 支持任務執行后的自定義邏輯
- 兼容InheritableThreadLocal

三、TransmittableThreadLocal原理
1. 核心類結構

- `TransmittableThreadLocal`:繼承自InheritableThreadLocal
- `TtlRunnable`/`TtlCallable`:裝飾器模式包裝Runnable和Callable
- `Transmitter`:提供capture/replay/restore機制

2. 工作原理

TTL的核心思想是"捕獲-傳遞-恢復":

1. 捕獲(Capture):在任務提交到線程池時,捕獲當前線程的所有TTL變量
2. 傳遞(Transmit):將捕獲的值傳遞給線程池中的線程
3. 恢復(Replay):線程池中的線程在執行任務前,將TTL值恢復
4. 回滾(Restore):任務執行完成后,恢復線程原來的TTL值

3. 實現機制
// 偽代碼展示TTL工作原理
public class TtlRunnable implements Runnable {private final Runnable runnable;private final Object captured;public TtlRunnable(Runnable runnable) {this.runnable = runnable;this.captured = TransmittableThreadLocal.Transmitter.capture();}public void run() {Object backup = TransmittableThreadLocal.Transmitter.replay(captured);try {runnable.run();} finally {TransmittableThreadLocal.Transmitter.restore(backup);}}
}
四、使用方式與示例
1. 基本使用
// 1. 創建TransmittableThreadLocal變量
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();// 2. 設置值
context.set("value-set-in-parent");// 3. 包裝Runnable/Callable
Runnable task = () -> {System.out.println("獲取TTL值: " + context.get());
};
Runnable ttlTask = TtlRunnable.get(task);// 4. 提交到線程池
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(ttlTask);
executor.shutdown();
2. 線程池集成

更優雅的方式是使用TtlExecutors包裝線程池:

ExecutorService executorService = Executors.newCachedThreadPool();
// 包裝線程池
ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(executorService);TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();context.set("value-set-in-parent");ttlExecutorService.execute(() -> {// 可以獲取到父線程設置的上下文System.out.println(context.get());
});
3. 異步場景示例
// 初始化TTL上下文
TransmittableThreadLocal<String> requestId = new TransmittableThreadLocal<>();
TransmittableThreadLocal<User> userInfo = new TransmittableThreadLocal<>();// 設置值
requestId.set("REQ-123456");
userInfo.set(new User("張三", "admin"));// 異步處理
CompletableFuture.runAsync(() -> {System.out.println("異步任務中獲取requestId: " + requestId.get());System.out.println("異步任務中獲取userInfo: " + userInfo.get());},TtlExecutors.getTtlExecutorService(ForkJoinPool.commonPool())
).join();
五、使用經驗與最佳實踐
1. 初始化建議
// 推薦使用withInitial初始化
private static final TransmittableThreadLocal<SimpleDateFormat> DATE_FORMATTER =?TransmittableThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
2. 內存管理

- 及時remove:任務完成后調用remove()避免內存泄漏
- 避免存儲大對象:TTL變量應保持輕量級

try {// 使用TTL
} finally {ttlVariable.remove();
}
3. 與線程池配合
// 創建線程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 包裝線程池
ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(executor);// 使用包裝后的線程池
ttlExecutor.execute(() -> {// 可以獲取TTL值
});
4. 性能考慮

- TTL會帶來一定的性能開銷(約5%)
- 高并發場景下應評估是否必要
- 考慮使用更輕量的解決方案(如方法參數傳遞)

六、常見問題與避坑指南
1. 內存泄漏

問題表現:線程池中的線程長期存活,TTL變量一直存在

解決方案:
?

try {// 業務代碼
} finally {ttlVariable.remove();
}
2. 線程池未包裝

問題表現:直接使用線程池提交任務,TTL值丟失

錯誤示例:
?

executor.execute(task); // 直接提交,TTL失效

正確做法:

executor.execute(TtlRunnable.get(task)); // 包裝后提交
// 或
ttlExecutor.execute(task);
3. 與第三方框架集成

問題表現:Spring的@Async、Hystrix等框架中TTL失效

解決方案:
- 自定義線程池包裝器
- 使用AOP攔截增強

@Bean
public Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 配置executorreturn TtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());
}
4. 值覆蓋問題

問題表現:多個任務共享線程時,TTL值被覆蓋

解決方案:
- 確保每次任務執行后恢復原值(TTL已自動處理)
- 避免在任務中修改TTL值影響其他任務

七、適用場景
1. 分布式跟蹤
// 設置traceId
TransmittableThreadLocal<String> traceId = new TransmittableThreadLocal<>();void processRequest(Request request) {traceId.set(request.getTraceId());// 異步處理不影響traceId傳遞asyncService.process(request);
}
2. 用戶上下文傳遞
class UserContextHolder {private static final TransmittableThreadLocal<User> CURRENT_USER = new TransmittableThreadLocal<>();public static void set(User user) {CURRENT_USER.set(user);}public static User get() {return CURRENT_USER.get();}public static void clear() {CURRENT_USER.remove();}
}
3. 多租戶系統
// 租戶上下文
public class TenantContext {private static final TransmittableThreadLocal<String> TENANT_ID = new TransmittableThreadLocal<>();public static void setTenantId(String tenantId) {TENANT_ID.set(tenantId);}public static String getTenantId() {return TENANT_ID.get();}
}// 業務代碼中無需顯式傳遞tenantId
public void businessMethod() {String tenantId = TenantContext.getTenantId();// 使用tenantId
}
4. 日志增強
// 日志上下文
public class LogContext {private static final TransmittableThreadLocal<Map<String, String>> LOG_CONTEXT =?TransmittableThreadLocal.withInitial(HashMap::new);public static void put(String key, String value) {LOG_CONTEXT.get().put(key, value);}public static Map<String, String> getContext() {return new HashMap<>(LOG_CONTEXT.get());}
}// 日志切面
@Aspect
@Component
public class LogAspect {@Around("execution(* com.example..*.*(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {MDC.setContextMap(LogContext.getContext());try {return pjp.proceed();} finally {MDC.clear();}}
}
八、性能優化建議

1. 減少TTL變量數量:只將必要的數據放入TTL
2. 使用基本類型:避免復雜對象
3. 對象復用:對于頻繁使用的對象,考慮對象池
4. 合理使用remove:長時間存活的線程池要定期清理

九、與其他技術對比
特性ThreadLocalInheritableThreadLocalTransmittableThreadLocal
線程隔離支持支持支持
父子線程傳遞不支持支持支持
線程池支持不支持不支持支持
執行前后自定義邏輯不支持不支持支持
性能開銷中高
十、總結

TransmittableThreadLocal是解決線程池環境下上下文傳遞的強大工具,合理使用可以簡化編程模型,但需要注意內存管理和性能影響。關鍵點:

1. 理解"捕獲-傳遞-恢復"機制
2. 線程池必須通過TtlRunnable/TtlCallable或TtlExecutors包裝
3. 及時清理避免內存泄漏
4. 評估性能影響,避免濫用

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

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

相關文章

Linux下的I/O復用技術之epoll

I/O多路復用 指在單個線程或進程中&#xff0c;同時處理多個I/O操作的技術。 旨在提高程序處理多個并發I/O操作的能力&#xff0c;避免程序因等待某個I/O操作而被阻塞。在傳統的I/O模型中當程序進行I/O操作時(如讀取文件、接受網路數據等)&#xff0c;如果數據還未準備好&…

用 C 語言實現通用的冒泡排序算法

在日常編程中&#xff0c;排序算法是一個非常常見且重要的工具。雖然有許多排序算法可以選擇&#xff0c;但如果你需要一個能夠處理不同數據類型的排序算法&#xff0c;如何設計一個通用的排序算法呢&#xff1f;今天我們將實現一個通用的冒泡排序算法&#xff0c;支持不同數據…

C# 變量全解析:聲明、初始化與使用

在多用途的編程語言中&#xff0c;程序存取數據是一項基礎且關鍵的功能&#xff0c;而這一功能主要通過變量來實現。本文將全面深入地探討 C# 中的變量&#xff0c;包括變量的種類、聲明、初始化、自動初始化、多變量聲明以及如何使用變量的值。 變量概述 變量是一個名稱&…

Dify中的文本分詞處理技術詳解

Dify中的文本分詞處理技術詳解 引言核心架構概覽索引處理器工廠 文本分詞技術詳解基礎分詞器增強型遞歸字符分詞器固定分隔符文本分詞器遞歸分割算法 索引處理器中的分詞應用特殊索引處理器的分詞特點問答索引處理器父子索引處理器 分詞技術的應用場景技術亮點與優勢總結 引言 …

如何打包python程序為可執行文件

將 Python 程序打包為可執行文件是一個常見需求&#xff0c;尤其是在希望將應用程序分享給不具備 Python 環境的用戶時。以下是使用 PyInstaller 工具將 Python 程序打包為可執行文件的步驟。 步驟 1&#xff1a;安裝 PyInstaller 如果您還沒有安裝 PyInstaller&#xff0c;請…

美團Java后端二面面經!

場景題是面試的大頭&#xff0c;建議好好準備 Q. [美團]如何設計一個外賣訂單的并發扣減庫存系統&#xff1f; Q.[美團]為啥初始標記和重新標記需要STW&#xff1f; Q.[美團]騎手位置實時更新&#xff0c;如何保證高并發寫入&#xff1f; Q.[美團]訂單表數據量過大導致查詢…

在應用運維過程中,業務數據修改的證據留存和數據留存

在應用運維過程中,業務數據修改的證據留存和數據留存至關重要,以下是相關介紹: 一、證據留存 操作日志記錄 : 詳細記錄每一次業務數據修改的操作日志,包括操作人員、操作時間、修改內容、修改前后數據的對比等。例如,某公司業務系統中,操作日志會精確記錄員工小張在 2…

Eigen迭代求解器類

1. 迭代求解器核心類概覽 Eigen 提供多種迭代法求解稀疏線性方程組 AxbAxb&#xff0c;適用于大規模稀疏矩陣&#xff1a; 求解器類適用矩陣類型算法關鍵特性ConjugateGradient對稱正定&#xff08;SPD&#xff09;共軛梯度法&#xff08;CG&#xff09;高精度&#xff0c;內…

ORACLE數據庫備份入門:第四部分:2-備份場景舉例

下面以4個常見的場景為例&#xff0c;介紹如何規劃備份方案。備份方案沒有標準答案&#xff0c;需要根據實現情況來制定&#xff0c;也和管理員的個人使用習慣有很大相關性。 1 交易型數據庫備份 以銀行的交易系統為例&#xff0c;除了前一章節提到的關于RPO和RTO的指標外&am…

小白如何學會完整挪用Github項目?(以pix2pix為例)

[目錄] 0.如何完整地復現/應用一個Github項目 1.建立適用于項目的環境 2.數據準備與模型訓練階段 3.訓練過程中的一些命令行調試必備知識0.如何完整地復現/應用一個Github項目 前日在健身房的組間同一位好友交流時&#xff0c;得到了一個一致結論—— ** Github \texttt{Githu…

藍橋杯 5. 交換瓶子

交換瓶子 原題目鏈接 題目描述 有 N 個瓶子&#xff0c;編號為 1 ~ N&#xff0c;放在架子上。 例如有 5 個瓶子&#xff0c;當前排列為&#xff1a; 2 1 3 5 4每次可以拿起 2 個瓶子&#xff0c;交換它們的位置。 要求通過若干次交換&#xff0c;使得瓶子的編號從小到大…

Linux 系統滲透提權

Linux 系統滲透提權 比賽題庫-Linux 系統滲透提權 文章目錄 Linux 系統滲透提權比賽題庫-Linux 系統滲透提權 前言一、解題過程1.使用滲透機對服務器信息收集&#xff0c;并將服務器中 SSH 服務端口號作為 flag 提 交&#xff1b;2.使用滲透機對服務器信息收集&#xff0c;并將…

華為OD機試真題——查找接口成功率最優時間段(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 A卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C、GO六種語言的最佳實現方式&#xff1b; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析&#xff1b; 本文收錄于專欄&#xff1a;《2025華為OD真題目錄…

華為OD機試真題——繪圖機器(2025A卷:100分)Java/python/JavaScript/C++/C/GO最佳實現

2025 A卷 100分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析&#xff1b; 并提供Java、python、JavaScript、C、C語言、GO六種語言的最佳實現方式&#xff01; 本文收錄于專欄&#xff1a;《2025華為OD真題目錄全流程解析/備考攻略/經驗…

基于 Python(selenium) 的百度新聞定向爬蟲:根據輸入的關鍵詞在百度新聞上進行搜索,并爬取新聞詳情頁的內容

該項目能夠根據輸入的關鍵詞在百度新聞上進行搜索,并爬取新聞詳情頁的內容。 一、項目準備 1. 開發環境配置 操作系統:支持 Windows、macOS、Linux 等主流操作系統,本文以 Windows 為例進行說明。Python 版本:建議使用 Python 3.8 及以上版本,以確保代碼的兼容性和性能。…

MySQL表的操作 -- 表的增刪改查

目錄 1. 表的創建2. 表的查看3. 表的修改4. 表的刪除5. 總結 1. 表的創建 1.查看字符集及效驗規則 2. 表的創建 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校驗規則 engine 存儲引擎;創建用戶表1 創建用…

如何解決極狐GitLab 合并沖突?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 合并沖突 (BASIC ALL) 合并沖突發生在合并請求的兩個分支&#xff08;源分支和目標分支&#xff09;對相同代碼行進行了不同…

oracle不同數據庫版本的自增序列

-- 查看數據庫版本 SELECT * FROM v$version WHERE banner LIKE Oracle%; 1. Oracle 12c及以上版本支持 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id NUMBER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, -- 語法 id NUMBER GENER…

VIC-3D非接觸全場應變測量系統用于小尺寸測量之電子元器件篇—研索儀器DIC數字圖像相關技術

在5G通信、新能源汽車電子、高密度集成電路快速迭代的今天&#xff0c;電子元件的尺寸及連接工藝已進入亞毫米級競爭階段&#xff0c;這種小尺寸下的力學性能評估對測量方式的精度有更高的要求&#xff0c;但傳統應變測量手段常因空間尺寸限制及分辨率不足難以捕捉真實形變場。…

pod 創建私有庫指南

步驟 參考&#xff1a;iOS Pod 私有庫創建指南-百度開發者中心 下面主要是對參考鏈接里面的解釋&#xff1a; 創建兩個倉庫&#xff1a; 一個叫podframe.git&#xff0c;用來存放自定義的framework&#xff0c;比如TestPodFrame.framework一個叫podspec.git&#xff0c;用來…