JDK的InheritableThreadLocal類能夠完成父子線程值的傳遞。 但對於使用線程池等會緩存線程的組件的狀況,線程由線程池建立好,而且線程是緩存起來反覆使用的;這時父子線程關係的上下文傳遞已經沒有意義,應用中要作上下文傳遞,其實是在把 任務提交給線程池時的上下文傳遞到 任務執行時。javascript
本庫提供的TransmittableThreadLocal類繼承並增強InheritableThreadLocal類,解決上述的問題,使用詳見User Guide。html
歡迎java
建議和提問,提交Issuegit
貢獻和改進,Fork後提經過Pull Request貢獻代碼github
在ThreadLocal的需求場景便是TTL的潛在需求場景,若是你的業務須要『在使用線程池等會緩存線程的組件狀況下傳遞ThreadLocal』則是TTL目標場景。shell
下面是幾個典型場景例子。api
分佈式跟蹤系統緩存
應用容器或上層框架跨應用代碼給下層SDK傳遞信息架構
日誌收集記錄系統上下文oracle
各個場景的展開說明參見子文檔 需求場景。
使用類TransmittableThreadLocal來保存上下文,並跨線程池傳遞。
TransmittableThreadLocal繼承InheritableThreadLocal,使用方式也相似。
比InheritableThreadLocal,添加了protected方法copy,用於定製 任務提交給線程池時的上下文傳遞到 任務執行時時的拷貝行爲,缺省是傳遞的是引用。
具體使用方式見下面的說明。
父線程給子線程傳遞值。
示例代碼:
// 在父線程中設置
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // ===================================================== // 在子線程中能夠讀取, 值是"value-set-in-parent" String value = parent.get();
這是實際上是InheritableThreadLocal的功能,應該使用InheritableThreadLocal來完成。
但對於使用了異步執行(每每使用線程池完成)的狀況,線程由線程池建立好,而且線程是緩存起來反覆使用的。
這時父子線程關係的上下文傳遞已經沒有意義,應用中要作上下文傳遞,其實是在把 任務提交給線程池時的上下文傳遞到任務執行時。 解決方法參見下面的這幾種用法。
使用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();
省去每次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();
這種方式,實現線程池的傳遞是透明的,代碼中沒有修飾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。
因爲Runnable和Callable的修飾代碼,是在線程池類中插入的。下面的狀況會讓插入的代碼被繞過,傳遞會失效。
用戶代碼中繼承java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor, 覆蓋了execute、submit、schedule等提交任務的方法,而且沒有調用父類的方法。
修改線程池類的實現,execute、submit、schedule等提交任務的方法禁止這些被覆蓋,能夠規避這個問題。
目前,沒有修飾java.util.Timer類,使用Timer時,TTL會有問題。
當前版本的Java API文檔地址: http://alibaba.github.io/transmittable-thread-local/apidocs/
示例:
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.0.0</version> </dependency>
能夠在 search.maven.org 查看可用的版本。
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_5一、1.7.0_45能夠運行。
# 1.7.0_45仍是有JavaLaunchHelper的出錯信息,但不影響運行。