功能
在使用線程池等會緩存線程的組件情況下,提供ThreadLocal值的傳遞功能。
JDK的InheritableThreadLocal類可以完成父子線程值的傳遞。 但對於使用線程池等會緩存線程的組件的情況,線程由線程池創建好,並且線程是緩存起來反復使用的;這時父子線程關系的上下文傳遞已經沒有意義,應用中要做上下文傳遞,實際上是在把 任務提交給線程池時的上下文傳遞到 任務執行時。
本庫提供的TransmittableThreadLocal類繼承並加強InheritableThreadLocal類,解決上述的問題,使用詳見User Guide。
歡迎
-
建議和提問,提交Issue
-
貢獻和改進,Fork后提通過Pull Request貢獻代碼
需求場景
在ThreadLocal的需求場景即是TTL的潛在需求場景,如果你的業務需要『在使用線程池等會緩存線程的組件情況下傳遞ThreadLocal』則是TTL目標場景。
下面是幾個典型場景例子。
-
分布式跟蹤系統
-
應用容器或上層框架跨應用代碼給下層SDK傳遞信息
-
日志收集記錄系統上下文
各個場景的展開說明參見子文檔 需求場景。
User Guide
使用類TransmittableThreadLocal來保存上下文,並跨線程池傳遞。
TransmittableThreadLocal繼承InheritableThreadLocal,使用方式也類似。
比InheritableThreadLocal,添加了protected方法copy,用於定制 任務提交給線程池時的上下文傳遞到 任務執行時時的拷貝行為,缺省是傳遞的是引用。
具體使用方式見下面的說明。
1. 簡單使用
父線程給子線程傳遞值。
示例代碼:
// 在父線程中設置
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // ===================================================== // 在子線程中可以讀取, 值是"value-set-in-parent" String value = parent.get();
這是其實是InheritableThreadLocal的功能,應該使用InheritableThreadLocal來完成。
但對於使用了異步執行(往往使用線程池完成)的情況,線程由線程池創建好,並且線程是緩存起來反復使用的。
這時父子線程關系的上下文傳遞已經沒有意義,應用中要做上下文傳遞,實際上是在把 任務提交給線程池時的上下文傳遞到任務執行時。 解決方法參見下面的這幾種用法。
2. 保證線程池中傳遞值
2.1 修飾Runnable和Callable
使用com.alibaba.ttl.TtlRunnable和com.alibaba.ttl.TtlCallable來修飾傳入線程池的Runnable和Callable。
示例代碼:
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent"); Runnable task = new Task("1"); // 額外的處理,生成修飾了的對象 ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable); // ===================================================== // Task中可以讀取, 值是"value-set-in-parent" String value = parent.get();
上面演示了Runnable,Callable的處理類似
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Callable call = new Call("1"); // 額外的處理,生成修飾了的對象 ttlCallable Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // ===================================================== // Call中可以讀取, 值是"value-set-in-parent" String value = parent.get();
整個過程的完整時序圖
2.2 修飾線程池
省去每次Runnable和Callable傳入線程池時的修飾,這個邏輯可以在線程池中完成。
通過工具類com.alibaba.ttl.threadpool.TtlExecutors完成,有下面的方法:
-
getTtlExecutor:修飾接口Executor
-
getTtlExecutorService:修飾接口ExecutorService
-
ScheduledExecutorService:修飾接口ScheduledExecutorService
示例代碼:
ExecutorService executorService = ... // 額外的處理,生成修飾了的對象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Runnable task = new Task("1"); Callable call = new Call("2"); executorService.submit(task); executorService.submit(call); // ===================================================== // Task或是Call中可以讀取, 值是"value-set-in-parent" String value = parent.get();
2.3 使用Java Agent來修飾JDK線程池實現類
這種方式,實現線程池的傳遞是透明的,代碼中沒有修飾Runnable或是線程池的代碼。
# 即可以做到應用代碼 無侵入,后面文檔有結合實際場景的架構對這一點的說明。
示例代碼:
// 框架代碼
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // 應用代碼 ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new Task("1"); Callable call = new Call("2"); executorService.submit(task); executorService.submit(call); // ===================================================== // Task或是Call中可以讀取, 值是"value-set-in-parent" String value = parent.get();
Demo參見AgentDemo.java。
目前Agent中,修飾了jdk中的兩個線程池實現類(實現代碼在TtlTransformer.java):
-
java.util.concurrent.ThreadPoolExecutor
-
java.util.concurrent.ScheduledThreadPoolExecutor
在Java的啟動參數加上:
-
-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.x.jar
-
-javaagent:/path/to/transmittable-thread-local-2.x.x.jar
注意:
-
Agent修改是JDK的類,類中加入了引用TTL的代碼,所以TTL Agent的Jar要加到bootclasspath上。
Java命令行示例如下:
java -Xbootclasspath/a:transmittable-thread-local-2.0.0.jar \ -javaagent:transmittable-thread-local-2.0.0.jar \ -cp classes \ com.alibaba.ttl.threadpool.agent.demo.AgentDemo
有Demo演示『使用Java Agent來修飾線程池實現類』,執行工程下的腳本run-agent-demo.sh即可運行Demo。
Java Agent的使用方式在什么情況下TTL會失效
由於Runnable和Callable的修飾代碼,是在線程池類中插入的。下面的情況會讓插入的代碼被繞過,傳遞會失效。
-
用戶代碼中繼承java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor, 覆蓋了execute、submit、schedule等提交任務的方法,並且沒有調用父類的方法。
修改線程池類的實現,execute、submit、schedule等提交任務的方法禁止這些被覆蓋,可以規避這個問題。 -
目前,沒有修飾java.util.Timer類,使用Timer時,TTL會有問題。
Java API Docs
當前版本的Java API文檔地址: http://alibaba.github.io/transmittable-thread-local/apidocs/
Maven依賴
示例:
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.0.0</version> </dependency>
可以在 search.maven.org 查看可用的版本。
FAQ
-
Mac OS X下,使用javaagent,可能會報JavaLaunchHelper的出錯信息。
JDK Bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021205
可以換一個版本的JDK。我的開發機上1.7.0_40有這個問題,1.6.0_51、1.7.0_45可以運行。
# 1.7.0_45還是有JavaLaunchHelper的出錯信息,但不影響運行。