TransmittableThreadLocal 是Alibaba開源的、用於解決 「在使用線程池等會緩存線程的組件狀況下傳遞ThreadLocal」 問題的 InheritableThreadLocal 擴展。若但願 TransmittableThreadLocal 在線程池與主線程間傳遞,需配合 TtlRunnable 和 TtlCallable 使用。html
下面是幾個典型場景例子。java
JDK
的InheritableThreadLocal
類能夠完成父線程到子線程的值傳遞。但對於使用線程池等會池化複用線程的組件的狀況,線程由線程池建立好,而且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal
值傳遞已經沒有意義,應用須要的其實是把 任務提交給線程池時的ThreadLocal
值傳遞到 任務執行時。git
下面分析下InheritableThreadLoc
InheritableThreadLocal類重寫了ThreadLocal的3個函數:github
/** * 該函數在父線程建立子線程,向子線程複製InheritableThreadLocal變量時使用 */ protected T childValue(T parentValue) { return parentValue; } /** * 因爲重寫了getMap,操做InheritableThreadLocal時, * 將隻影響Thread類中的inheritableThreadLocals變量, * 與threadLocals變量再也不有關係 */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * 相似於getMap,操做InheritableThreadLocal時, * 將隻影響Thread類中的inheritableThreadLocals變量, * 與threadLocals變量再也不有關係 */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }
注意:重寫了getMap()和createMap()兩個函數,說到InheritableThreadLocal,還要從Thread類提及:bootstrap
public class Thread implements Runnable { ......(其餘源碼) /* * 當前線程的ThreadLocalMap,主要存儲該線程自身的ThreadLocal */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal,自父線程集成而來的ThreadLocalMap, * 主要用於父子線程間ThreadLocal變量的傳遞 * 本文主要討論的就是這個ThreadLocalMap */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ......(其餘源碼) }
Thread類中包含 threadLocals 和 inheritableThreadLocals 兩個變量,其中 inheritableThreadLocals 即主要存儲可自動向子線程中傳遞的ThreadLocal.ThreadLocalMap。
接下來看一下父線程建立子線程的流程,咱們從最簡單的方式提及:api
用戶建立Thread緩存
hread thread = new Thread();
** * Allocates a new {@code Thread} object. This constructor has the same * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} * {@code (null, null, gname)}, where {@code gname} is a newly generated * name. Automatically generated names are of the form * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer. */ public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }
Thread初始化oracle
/** * 默認狀況下,設置inheritThreadLocals可傳遞 */ private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } /** * 初始化一個線程. * 此函數有兩處調用, * 一、上面的 init(),不傳AccessControlContext,inheritThreadLocals=true * 二、傳遞AccessControlContext,inheritThreadLocals=false */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ......(其餘代碼) if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ......(其餘代碼) }
能夠看到,採用默認方式產生子線程時,inheritThreadLocals=true;若此時父線程inheritableThreadLocals不爲空,則將父線程inheritableThreadLocals傳遞至子線程。框架
讓咱們繼續追蹤createInheritedMap分佈式
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } /** * 構建一個包含全部parentMap中Inheritable ThreadLocals的ThreadLocalMap * 該函數只被 createInheritedMap() 調用. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); // ThreadLocalMap 使用 Entry[] table 存儲ThreadLocal table = new Entry[len]; // 逐一複製 parentMap 的記錄 for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { // 可能會有同窗好奇此處爲什麼使用childValue,而不是直接賦值, // 畢竟childValue內部也是直接將e.value返回; // 我的理解,主要爲了減輕閱讀代碼的難度 Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
從ThreadLocalMap可知,子線程將parentMap中的全部記錄逐一複製至自身線程。InheritableThreadLocal主要用於子線程建立時,須要自動繼承父線程的ThreadLocal變量,方便必要信息的進一步傳遞。
接下來提供的TransmittableThreadLocal
類繼承並增強InheritableThreadLocal
類,解決上述的問題。
使用類TransmittableThreadLocal
來保存值,並跨線程池傳遞。
TransmittableThreadLocal
繼承InheritableThreadLocal
,使用方式也相似。
相比InheritableThreadLocal
,添加了
protected
方法copy
ThreadLocal
值傳遞到 任務執行時 的拷貝行爲,缺省傳遞的是引用。protected
方法beforeExecute
/afterExecute
Runnable
/Callable
)的前/後的生命週期回調,缺省是空操做。父線程給子線程傳遞值。
示例代碼:
// 在父線程中設置 TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // ===================================================== // 在子線程中能夠讀取,值是"value-set-in-parent" String value = parent.get();
這是實際上是InheritableThreadLocal
的功能,應該使用InheritableThreadLocal
來完成。
但對於使用線程池等會池化複用線程的組件的狀況,線程由線程池建立好,而且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal
值傳遞已經沒有意義,應用須要的其實是把 任務提交給線程池時的ThreadLocal
值傳遞到 任務執行時。
解決方法參見下面的這幾種用法。
Runnable
和Callable
使用TtlRunnable
和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
getTtlScheduledExecutorService
:修飾接口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();
Java Agent
來修飾JDK
線程池實現類這種方式,實現線程池的傳遞是透明的,代碼中沒有修飾Runnable
或是線程池的代碼。便可以作到應用代碼 無侵入。
# 關於 無侵入 的更多說明參見文檔Java Agent
方式對應用代碼無侵入。
示例代碼:
// ## 1. 框架上層邏輯,後續流程框架調用業務 ## TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>(); context.set("value-set-in-parent"); // ## 2. 應用邏輯,後續流程業務調用框架下層邏輯 ## ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new Task("1"); Callable call = new Call("2"); executorService.submit(task); executorService.submit(call); // ## 3. 框架下層邏輯 ## // Task或是Call中能夠讀取,值是"value-set-in-parent" String value = context.get();
Demo參見AgentDemo.kt
。執行工程下的腳本scripts/run-agent-demo.sh
便可運行Demo。
目前TTL Agent
中,修飾了JDK
中的線程池實現以下:
java.util.concurrent.ThreadPoolExecutor
和 java.util.concurrent.ScheduledThreadPoolExecutor
TtlExecutorTransformlet.java
java.util.concurrent.ForkJoinTask
(對應的線程池組件是java.util.concurrent.ForkJoinPool
)TtlForkJoinTransformlet.java
java.util.TimerTask
的子類(對應的線程池組件是java.util.Timer
)TtlTimerTaskTransformlet.java
TimerTask
的修飾,使用Agent
參數ttl.agent.enable.timer.task
開啓:-javaagent:path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true
。TTL Agent
參數的配置說明詳見TtlAgent.java
的JavaDoc。關於
java.util.TimerTask
/java.util.Timer
Timer
是JDK 1.3
的老類,不推薦使用Timer
類。推薦用
ScheduledExecutorService
。
ScheduledThreadPoolExecutor
實現更強壯,而且功能更豐富。 如支持配置線程池的大小(Timer
只有一個線程);Timer
在Runnable
中拋出異常會停止定時執行。更多說明參見10. Mandatory Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. - Alibaba Java Coding Guidelines。
boot class path
設置由於修飾了JDK
的標準庫的類,標準庫由bootstrap class loader
加載;上面修飾後的JDK
類引用了TTL
的代碼,因此TTL
的Jar
須要加到boot class path
上。
TTL
從v2.6.0
開始,加載TTL Agent
會自動把本身的Jar
設置到boot class path
上。
注意:不能修改從Maven
庫下載的TTL
的Jar
的文件名(形如transmittable-thread-local-2.x.x.jar
)。 若是修改了,則須要本身手動經過-Xbootclasspath JVM
參數來顯式配置(就像TTL
以前的版本的作法同樣)。
實現是經過指定TTL Java Agent Jar
文件裏manifest
文件(META-INF/MANIFEST.MF
)的Boot-Class-Path
屬性:
Boot-Class-Path
A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed.
Java
的啓動參數配置
在Java
的啓動參數加上:-javaagent:path/to/transmittable-thread-local-2.x.x.jar
。
若是修改了下載的TTL
的Jar
的文件名(transmittable-thread-local-2.x.x.jar
),則須要本身手動經過-Xbootclasspath JVM
參數來顯式配置:
好比修改文件名成ttl-foo-name-changed.jar
,則還加上Java
的啓動參數:-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar
Java
命令行示例以下:
java -javaagent:path/to/transmittable-thread-local-2.x.x.jar \ -cp classes \ com.alibaba.ttl.threadpool.agent.demo.AgentDemo
或是
java -javaagent:path/to/ttl-foo-name-changed.jar \ -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \ -cp classes \ com.alibaba.ttl.threadpool.agent.demo.AgentDemo
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.10.2</version> </dependency>