ThreadLocal
ThreadLocal是線程本地變量蛉签,每個(gè)線程往這個(gè)ThreadLocal中讀寫是線程隔離,互相之間不會(huì)影響的沥寥。它提供了一種將可變數(shù)據(jù)通過(guò)每個(gè)線程有自己的獨(dú)立副本從而實(shí)現(xiàn)線程封閉的機(jī)制碍舍。
Thread類有一個(gè)類型為ThreadLocal.ThreadLocalMap的實(shí)例變量threadLocals,也就是說(shuō)每個(gè)線程有一個(gè)自己的ThreadLocalMap邑雅。ThreadLocalMap參照HashMap的實(shí)現(xiàn)片橡,ThreadLocalMap的key為ThreadLocal,value為代碼中放入的值(實(shí)際上key并不是ThreadLocal本身淮野,而是它的一個(gè)弱引用)捧书。每個(gè)線程在往某個(gè)ThreadLocal里塞值的時(shí)候,都會(huì)往自己的ThreadLocalMap里存骤星,讀也是以某個(gè)ThreadLocal作為引用经瓷,在自己的map里找對(duì)應(yīng)的key,從而實(shí)現(xiàn)了線程隔離洞难。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類舆吮。ThreadLocalMap提供了一種為ThreadLocal定制的高效實(shí)現(xiàn),并且自帶一種基于弱引用的垃圾清理機(jī)制。
ThreadLocalMap是類似HashMap實(shí)現(xiàn)的另一種map實(shí)現(xiàn)歪泳,有自己的key和value萝勤,key為ThreadLocal,value為代碼中放入的值。和HashMap一樣有一個(gè)table數(shù)據(jù),有g(shù)et和set方法俐填,在key的hash沖突時(shí)往下尋找對(duì)應(yīng)對(duì)象槽位。
ThreadLocalMap里的節(jié)點(diǎn)Entry不同于HashMap里面的Entry趟径,繼承弱引用,是如下定義的癣防。
static class Entry extends WeakReference<java.lang.ThreadLocal<?>> {
// 往ThreadLocal里實(shí)際塞入的值
Object value;
Entry(java.lang.ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
為什么用弱引用
弱引用是Java中四檔引用的第三檔蜗巧,比軟引用更加弱一些,如果一個(gè)對(duì)象沒(méi)有強(qiáng)引用鏈可達(dá)蕾盯,那么一般活不過(guò)下一次GC幕屹。當(dāng)某個(gè)ThreadLocal已經(jīng)沒(méi)有強(qiáng)引用可達(dá),則隨著它被垃圾回收级遭,在ThreadLocalMap里對(duì)應(yīng)的Entry的鍵值會(huì)失效望拖,這為ThreadLocalMap本身的垃圾清理提供了便利。
ThreadLocal的get和set方法
public T get() {
Thread t = Thread.currentThread(); //獲取當(dāng)前線程
ThreadLocalMap map = getMap(t); // 獲取當(dāng)前線程對(duì)象的實(shí)例變量threadLocals
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
set方法邏輯同get方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
InheritableThreadLocal
InheritableThreadLocal繼承ThreadLocal挫鸽,ThreadLocal在線程內(nèi)實(shí)現(xiàn)一個(gè)局部變量说敏,可以在線程的任何地方來(lái)訪問(wèn),能夠減少參數(shù)的傳遞丢郊,InheritableThreadLocal在子線程和父線程之間共享線程實(shí)例本地變量盔沫,也同樣是為了減少參數(shù)的傳遞。
ThreadLocal使用的是Thread的實(shí)例變量threadLocals枫匾;InheritableThreadLocal使用的是Thread的實(shí)例變量inheritableThreadLocals架诞。這兩個(gè)變量類型都為ThreadLocal.ThreadLocalMap,用途不一樣干茉。
InheritableThreadLocal的實(shí)現(xiàn)如下:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
InheritableThreadLocal重載了getMap和createMap方法侈贷,操作的不再是threadLocals,而是inheritableThreadLocals等脂。
在Thread初始化時(shí)有以下邏輯。
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
這個(gè)保證了inheritableThreadLocals變量從父Thread傳遞到子Thread撑蚌。
Transmittable ThreadLocal(TTL)
ThreadLocal實(shí)現(xiàn)了線程本地變量隔離,每個(gè)線程可以擁有不會(huì)被別的線程干擾的線程值map上遥。
InheritableThreadLocal繼承ThreadLocal,InheritableThreadLocal可以在子線程和父線程之間共享線程實(shí)例本地變量争涌。
JDK的InheritableThreadLocal類可以完成父線程到子線程的值傳遞粉楚。但對(duì)于使用線程池等會(huì)池化復(fù)用線程的組件的情況,線程由線程池創(chuàng)建好,并且線程是池化起來(lái)反復(fù)使用的模软;這時(shí)父子線程關(guān)系的ThreadLocal
值傳遞已經(jīng)沒(méi)有意義伟骨,應(yīng)用需要的實(shí)際上是把 任務(wù)提交給線程池時(shí)的線程的ThreadLocal
值傳遞到 任務(wù)執(zhí)行時(shí)的Thread。
這種場(chǎng)景需要TransmittableThreadLocal
類繼承并加強(qiáng)InheritableThreadLocal
類燃异,解決上述的問(wèn)題携狭。
框架/中間件集成TTL(TransmittableThreadLocal)
傳遞,通過(guò)TransmittableThreadLocal.Transmitter
抓取當(dāng)前線程的所有TTL
值并在其他線程進(jìn)行回放回俐;在回放線程執(zhí)行完業(yè)務(wù)操作后逛腿,恢復(fù)為回放線程原來(lái)的TTL
值。
TransmittableThreadLocal.Transmitter
提供了所有TTL
值的抓取仅颇、回放和恢復(fù)方法(即CRR
操作):
-
capture
方法:抓取線程(線程A)的所有TTL
值单默。 -
replay
方法:在另一個(gè)線程(線程B)中,回放在capture
方法中抓取的TTL
值(回放的過(guò)程也就是把第一步抓取的線程實(shí)例本地變量設(shè)置到當(dāng)前線程的實(shí)例本地變量中)忘瓦,并返回 回放前TTL
值的備份 -
restore
方法:恢復(fù)線程B執(zhí)行replay
方法之前的TTL
值(即備份搁廓,把第二步返回的回放前TTL
值重新設(shè)置回池化中線程的實(shí)例本地變量)
整個(gè)過(guò)程的完整時(shí)序圖
原理簡(jiǎn)析
對(duì)Runnable和Callable進(jìn)行裝飾,形成TtlRunnable和TtlCallable耕皮,在這兩個(gè)裝飾類里面主要多了一個(gè)引用對(duì)象(存放線程切換時(shí)線程的本地變量引用)境蜕。
private final AtomicReference<Object> capturedRef; //用戶存放線程本地變量的map引用
再來(lái)看一下CRR
操作的核心源碼,CRR的代碼邏輯時(shí)在TransmittableThreadLocal的靜態(tài)內(nèi)部類Transmitter中:
capture
public static Object capture() {
Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
//holder是TransmittableThreadLocal類的靜態(tài)對(duì)象,類型為InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>明场,用于存儲(chǔ)線程的本地變量汽摹,這一份本地變量被TransmittableThreadLocal用于CRR操作
for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
captured.put(threadLocal, threadLocal.copyValue());
}
return captured;
}
repaly
public static Object replay(Object captured) {
@SuppressWarnings("unchecked")
Map<TransmittableThreadLocal<?>, Object> capturedMap = (Map<TransmittableThreadLocal<?>, Object>) captured;
Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();
for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
iterator.hasNext(); ) {
Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
TransmittableThreadLocal<?> threadLocal = next.getKey();
// backup
backup.put(threadLocal, threadLocal.get());
// clear the TTL value only in captured
if (!capturedMap.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// 把capture抓取的線程本地變量設(shè)置到當(dāng)前線程的本地變量中
for (Map.Entry<TransmittableThreadLocal<?>, Object> entry : capturedMap.entrySet()) {
@SuppressWarnings("unchecked")
TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal<Object>) entry.getKey();
threadLocal.set(entry.getValue());
}
// call beforeExecute callback
doExecuteCallback(true);
return backup;
}
restore
public static void restore(Object backup) {
@SuppressWarnings("unchecked")
Map<TransmittableThreadLocal<?>, Object> backupMap = (Map<TransmittableThreadLocal<?>, Object>) backup;
// call afterExecute callback
doExecuteCallback(false);
for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
iterator.hasNext(); ) {
Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
TransmittableThreadLocal<?> threadLocal = next.getKey();
// clear the TTL value only in backup
// avoid the extra value of backup after restore
if (!backupMap.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// 把replay步驟備份前`TTL`值重新設(shè)置回池化中線程的本地變量
for (Map.Entry<TransmittableThreadLocal<?>, Object> entry : backupMap.entrySet()) {
@SuppressWarnings("unchecked")
TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal<Object>) entry.getKey();
threadLocal.set(entry.getValue());
}
}
業(yè)務(wù)無(wú)感知
如果想讓TTL框架更通用,對(duì)已經(jīng)的業(yè)務(wù)場(chǎng)景進(jìn)行無(wú)縫支持苦锨。需要把對(duì)Runnable逼泣、Callable、ExecuteService等池化類的裝飾操作借助字節(jié)碼在JVM啟動(dòng)時(shí)自動(dòng)完成舟舒。
使用Java Agent來(lái)修飾JDK線程池實(shí)現(xiàn)類拉庶。這種方式實(shí)現(xiàn)線程池的傳遞是透明的,代碼中沒(méi)有修飾Runnable
或是線程池的代碼秃励。即可以做到應(yīng)用代碼 無(wú)侵入氏仗。
關(guān)于 無(wú)侵入 的更多說(shuō)明參見文檔Java Agent
方式對(duì)應(yīng)用代碼無(wú)侵入。后面會(huì)寫專門的文章介紹字節(jié)碼增強(qiáng)這塊的技術(shù)夺鲜,這里就不展開了皆尔。
參考資料
JDK源碼
https://github.com/alibaba/transmittable-thread-local#21-%E4%BF%AE%E9%A5%B0runnable%E5%92%8Ccallable