Java中使用阿里巴巴開源庫TransmittableThreadLocal:跨線程上下文傳遞講解
Java中使用阿里巴巴開源庫TransmittableThreadLocal跨線程上下文傳遞講解問題背景Java 的ThreadLocal只能在當(dāng)前線程內(nèi)讀寫數(shù)據(jù)。當(dāng)任務(wù)被提交到線程池執(zhí)行時(shí)子線程線程池中的工作線程無法讀取到父線程設(shè)置的 ThreadLocal 值。ThreadLocalStringcontextnewThreadLocal();context.set(user-007);executorService.submit(()-{System.out.println(context.get());// 輸出 null});JDK 提供了InheritableThreadLocal可以在new Thread()創(chuàng)建子線程時(shí)復(fù)制父線程的值。但線程池中的線程是復(fù)用的不是每次都新建的所以InheritableThreadLocal在線程池場景下也失效。注博客https://blog.csdn.net/badao_liumang_qizhiTransmittableThreadLocal 是什么TransmittableThreadLocalTTL是阿里巴巴開源的一個(gè)庫解決的核心問題是在線程池復(fù)用線程時(shí)仍然能正確傳遞 ThreadLocal 值。原理簡述在任務(wù)提交到線程池時(shí)捕獲當(dāng)前線程的 TTL 值快照在任務(wù)執(zhí)行前回放快照到工作線程任務(wù)執(zhí)行完畢后恢復(fù)工作線程原來的值。提交線程父 線程池工作線程子 ┌──────────────┐ ┌──────────────┐ │ TTL 007 │ │ TTL null │ │ │ │ │ │ submit(task) ├───┐ │ │ └──────────────┘ │ └──────────────┘ │ │ ① 提交時(shí)捕獲快照 {007} │ ② 執(zhí)行前回放 → TTL 007 │ ③ 執(zhí)行中task 讀到 007 │ ④ 執(zhí)行后恢復(fù) → TTL nullMaven 依賴dependencygroupIdcom.alibaba/groupIdartifactIdtransmittable-thread-local/artifactIdversion2.14.3/version/dependency基礎(chǔ)用法1. 聲明 TransmittableThreadLocalimportcom.alibaba.ttl.TransmittableThreadLocal;publicclassRequestContext{privatestaticfinalTransmittableThreadLocalStringUSER_IDnewTransmittableThreadLocal();privatestaticfinalTransmittableThreadLocalStringTRACE_IDnewTransmittableThreadLocal();publicstaticvoidsetUserId(StringuserId){USER_ID.set(userId);}publicstaticStringgetUserId(){returnUSER_ID.get();}publicstaticvoidsetTraceId(StringtraceId){TRACE_ID.set(traceId);}publicstaticStringgetTraceId(){returnTRACE_ID.get();}publicstaticvoidclear(){USER_ID.remove();TRACE_ID.remove();}}2. 包裝線程池TTL 需要對線程池進(jìn)行包裝才能生效有三種方式importcom.alibaba.ttl.threadpool.TtlExecutors;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;// 方式一包裝已有線程池推薦最常用ExecutorServicerawPoolExecutors.newFixedThreadPool(4);ExecutorServicettlPoolTtlExecutors.getTtlExecutorService(rawPool);// 方式二包裝 ScheduledExecutorServiceScheduledExecutorServicerawScheduledExecutors.newScheduledThreadPool(2);ScheduledExecutorServicettlScheduledTtlExecutors.getTtlScheduledExecutorService(rawScheduled);3. 使用示例publicclassDemo{privatestaticfinalExecutorServicepoolTtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4));publicstaticvoidmain(String[]args)throwsException{// 主線程設(shè)置上下文RequestContext.setUserId(007);RequestContext.setTraceId(trace-abc-123);// 提交到線程池子線程能讀到pool.submit(()-{System.out.println(RequestContext.getUserId());// 007System.out.println(RequestContext.getTraceId());// trace-abc-123}).get();// 修改后再提交子線程讀到新值RequestContext.setUserId(008);pool.submit(()-{System.out.println(RequestContext.getUserId());// 008}).get();RequestContext.clear();}}三種傳遞方式對比方式使用場景說明包裝線程池推薦首選TtlExecutors.getTtlExecutorService(pool)包裝 Runnable/Callable無法修改線程池時(shí)TtlRunnable.get(runnable)Java Agent無侵入全局生效JVM 啟動參數(shù)-javaagent:transmittable-thread-local-x.x.x.jar包裝 Runnable不改線程池importcom.alibaba.ttl.TtlRunnable;ExecutorServicerawPoolExecutors.newFixedThreadPool(4);RequestContext.setUserId(007);// 包裝任務(wù)而非線程池Runnabletask()-System.out.println(RequestContext.getUserId());RunnablettlTaskTtlRunnable.get(task);rawPool.submit(ttlTask);// 輸出 007Java Agent 方式零侵入JVM 啟動參數(shù)加上-javaagent:/path/to/transmittable-thread-local-2.14.3.jar無需包裝線程池或 Runnable所有 ThreadLocal 自動具備跨線程傳遞能力。適合無法修改代碼的場景或全局啟用。Spring Boot 集成配置異步線程池importcom.alibaba.ttl.threadpool.TtlExecutors;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importjava.util.concurrent.Executor;ConfigurationpublicclassThreadPoolConfig{Bean(asyncExecutor)publicExecutorasyncExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setCorePoolSize(8);executor.setMaxPoolSize(16);executor.setQueueCapacity(200);executor.setThreadNamePrefix(async-);executor.initialize();// 關(guān)鍵用 TTL 包裝returnTtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());}}攔截器設(shè)置上下文importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importorg.springframework.stereotype.Component;importorg.springframework.web.servlet.HandlerInterceptor;ComponentpublicclassContextInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){RequestContext.setUserId(request.getHeader(X-User-Id));RequestContext.setTraceId(request.getHeader(X-Trace-Id));returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){RequestContext.clear();// 必須清理防止內(nèi)存泄漏}}業(yè)務(wù)使用ServicepublicclassOrderService{AutowiredQualifier(asyncExecutor)privateExecutorasyncExecutor;publicvoidcreateOrder(OrderDTOdto){// 當(dāng)前HTTP線程設(shè)置的上下文// RequestContext.getUserId() 007// 異步處理子線程也能讀到 007CompletableFuture.runAsync(()-{StringuserIdRequestContext.getUserId();// 007 ?auditService.record(userId,創(chuàng)建訂單);},asyncExecutor);}}與 InheritableThreadLocal 的區(qū)別// 測試代碼InheritableThreadLocalStringitlnewInheritableThreadLocal();TransmittableThreadLocalStringttlnewTransmittableThreadLocal();ExecutorServicepoolExecutors.newFixedThreadPool(1);// 只有1個(gè)線程必定復(fù)用// 第一次提交線程首次創(chuàng)建ITL 和 TTL 都能傳遞itl.set(A);ttl.set(A);pool.submit(()-{System.out.println(ITLitl.get());// ASystem.out.println(TTLttl.get());// A}).get();// 第二次提交線程被復(fù)用ITL 還是舊值TTL 是新值itl.set(B);ttl.set(B);pool.submit(()-{System.out.println(ITLitl.get());// A舊值線程復(fù)用不會重新繼承System.out.println(TTLttl.get());// B正確每次提交重新捕獲}).get();特性ThreadLocalInheritableThreadLocalTransmittableThreadLocal當(dāng)前線程讀寫???new Thread 繼承???線程池復(fù)用傳遞???值更新后子線程感知-??適用場景場景說明鏈路追蹤traceId、spanId 跨線程透傳用戶身份傳遞HTTP 攔截器設(shè)置 userId異步任務(wù)中使用日志 MDC跨線程保持日志上下文字段一致多租戶隔離tenantId 在線程池中不丟失灰度/AB測試流量標(biāo)記在異步鏈路中傳遞注意事項(xiàng)必須清理HTTP 請求結(jié)束后調(diào)remove()防止線程池復(fù)用時(shí)數(shù)據(jù)泄漏到下一個(gè)請求不能跨 JVMTTL 只解決進(jìn)程內(nèi)跨線程問題跨 MQ、RPC 調(diào)用需要通過消息 Header 或 RPC Context 手動傳遞包裝要完整項(xiàng)目中所有線程池都需要包裝遺漏任何一個(gè)都會導(dǎo)致該路徑上下文丟失避免存大對象TTL 每次提交任務(wù)都會做快照拷貝淺拷貝存大對象有性能和一致性風(fēng)險(xiǎn)配合 RPC 框架Dubbo、gRPC 等通常有自己的 Context 傳遞機(jī)制TTL 負(fù)責(zé)進(jìn)程內(nèi)RPC Context 負(fù)責(zé)進(jìn)程間TTL 不能解決的問題進(jìn)程A (線程池內(nèi)傳遞 ?) 進(jìn)程B (需要額外手段) ┌─────────────────────┐ ┌─────────────────────┐ │ HTTP線程 → 線程池 │── MQ/RPC ──→│ 消費(fèi)線程 │ │ TTL 有效 ? │ │ TTL 管不到 ? │ └─────────────────────┘ └─────────────────────┘ 需要通過 MQ Header / RPC Attachment 傳遞跨進(jìn)程傳遞需要配合其他手段MQ UserProperty、RPC Attachment、HTTP Header 等TTL 只負(fù)責(zé)進(jìn)程內(nèi)線程間的透傳。

相關(guān)新聞

低壓工況下閥門密封性能的影響因素與試驗(yàn)評價(jià)方法

低壓工況下閥門密封性能的影響因素與試驗(yàn)評價(jià)方法

1. 問題背景在閥門壓力試驗(yàn)中,常見的一種認(rèn)識是:如果閥門能夠通過高壓密封試驗(yàn),那么低壓密封性能通常也不會存在問題。這一認(rèn)識并不適用于所有閥門結(jié)構(gòu)。閥門的密封性能取決于密封副接觸壓力、密封面形貌、關(guān)閉件定位、閥座預(yù)緊以及介質(zhì)壓差等…

2026/7/29 8:16:09 閱讀更多
PLC順序控制實(shí)戰(zhàn):定時(shí)器與比較指令實(shí)現(xiàn)電機(jī)順序啟停

PLC順序控制實(shí)戰(zhàn):定時(shí)器與比較指令實(shí)現(xiàn)電機(jī)順序啟停

1. 項(xiàng)目緣起:一個(gè)經(jīng)典但易錯(cuò)的工業(yè)控制場景在工廠的自動化產(chǎn)線上,我們經(jīng)常會遇到這樣的需求:幾臺電動機(jī)需要按照特定的順序啟動,比如先啟動主傳動電機(jī),再啟動送料電機(jī),最后啟動冷卻風(fēng)機(jī);而在停機(jī)…

2026/7/29 8:16:09 閱讀更多
STM32驅(qū)動SPI彩屏:從硬件連接到DMA刷屏的完整實(shí)戰(zhàn)指南

STM32驅(qū)動SPI彩屏:從硬件連接到DMA刷屏的完整實(shí)戰(zhàn)指南

1. 從點(diǎn)燈到點(diǎn)亮世界:為什么SPI彩屏是STM32入門的絕佳跳板 很多朋友學(xué)STM32,都是從點(diǎn)亮一個(gè)LED開始的。這當(dāng)然沒錯(cuò),但點(diǎn)亮LED之后呢?當(dāng)你能熟練地翻轉(zhuǎn)GPIO口,配置好定時(shí)器,甚至玩轉(zhuǎn)了串口通信,下…

2026/7/29 8:16:09 閱讀更多
ZX8002D單通道觸摸臺燈 化妝鏡 觸摸芯片帶鋰電池充電功能IC

ZX8002D單通道觸摸臺燈 化妝鏡 觸摸芯片帶鋰電池充電功能IC

一、ZX8002D方案概述 本方案以泛海微電子推出的ZX8002D單通道觸摸調(diào)光芯片為核心,針對LED臺燈、LED化妝鏡等小型LED照明產(chǎn)品設(shè)計(jì),實(shí)現(xiàn)單按鍵控制三檔亮度循環(huán)調(diào)節(jié)的功能。芯片集成了鋰電池充電管理模塊,支持2.5V-5V寬電壓輸入,無…

2026/7/29 9:16:11 閱讀更多
朝陽市錦藝宮月子中心實(shí)踐經(jīng)驗(yàn)分享

朝陽市錦藝宮月子中心實(shí)踐經(jīng)驗(yàn)分享

行業(yè)痛點(diǎn)分析在當(dāng)前朝陽市的月子中心領(lǐng)域,技術(shù)和服務(wù)面臨著多重挑戰(zhàn)。首先,護(hù)理人員的專業(yè)性和穩(wěn)定性是關(guān)鍵問題之一,由于缺乏系統(tǒng)化的培訓(xùn)和高流動性,導(dǎo)致服務(wù)質(zhì)量和專業(yè)性難以保證。其次,針對早產(chǎn)兒、低體重兒等特殊…

2026/7/29 9:16:11 閱讀更多
FPGA實(shí)戰(zhàn)(58):10G Ethernet XGMII PHY層 接口設(shè)計(jì)與仿真驗(yàn)證

FPGA實(shí)戰(zhàn)(58):10G Ethernet XGMII PHY層 接口設(shè)計(jì)與仿真驗(yàn)證

引言 隨著數(shù)據(jù)中心與高速互聯(lián)場景對帶寬需求的持續(xù)增長,10 Gigabit Ethernet(10GbE)已成為各類 FPGA 平臺的標(biāo)準(zhǔn)高速接口方案。IEEE 802.3ae 定義的 10GBASE-R 物理層采用 64B/66B 編碼,通過 XGMII(10 Gigabit Media Independent Interface)總線與 MAC 層交互——數(shù)據(jù)通…

2026/7/29 9:06:11 閱讀更多
面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

前兩個(gè)月,我在重構(gòu) AlgoMooc 網(wǎng)站過程中,發(fā)現(xiàn)一個(gè)問題:在 Claude Code 里把一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,結(jié)果可能比 1 個(gè) agent 從頭干到尾還慢? 大多數(shù)人的第一反應(yīng)是反過來的:活是并行干的&#…

2026/7/29 0:15:24 閱讀更多
# 鴻蒙 HarmonyOS 應(yīng)用開發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號與動畫渲染精講

# 鴻蒙 HarmonyOS 應(yīng)用開發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號與動畫渲染精講

一、應(yīng)用概述 骰子(Dice Roller) 是一款經(jīng)典的休閑娛樂應(yīng)用,模擬了真實(shí)擲骰子的過程。應(yīng)用投擲兩個(gè)骰子(六面標(biāo)準(zhǔn)骰),使用 Unicode 骰面符號直觀展示每個(gè)骰子的點(diǎn)數(shù),并伴有快速滾動的動畫效果?!?/p>

2026/7/29 0:15:24 閱讀更多