局限性
局限性一:父線程無法通過ThreadLocal向子線程傳遞線程私有數(shù)據(jù)
ThreadLocal本意上就是線程私有的數(shù)據(jù)(從命名上也可以看出來),每個(gè)線程維護(hù)著自己的一份副本,線程與線程之間數(shù)據(jù)隔離。父線程若要向子線程傳遞線程私有數(shù)據(jù),肯定是不能通過ThreadLocal自己來實(shí)現(xiàn)耍属,否則也就不能叫ThreadLocal
如果使用異步任務(wù)時(shí),就涉及到線程間的數(shù)據(jù)傳遞,此時(shí)ThreadLocal就無法滿足條件
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("foo");
Thread b = new Thread(() -> {
String str = tl.get();
// 企圖拿到父線程放進(jìn)去的foo恤煞,然而失敗了!
System.out.println(str);
});
b.start();
// 子線程B啟動(dòng)后施籍,父線程A休息3秒居扒,確保子線程執(zhí)行完畢
TimeUnit.SECONDS.sleep(3);
// 清理
tl.remove();
解決方案:使用InheritableThreadLocal
InheritableThreadLocal是ThreadLocal的子類,它就是用來解決父線程向子線程傳遞線程私有數(shù)據(jù)問題的法梯。示例代碼只改第一行:ThreadLocal<String> tl = new ThreadLocal<>();改為ThreadLocal<String> tl = new InheritableThreadLocal<>();苔货,其它不變,如下示:
ThreadLocal<String> tl = new InheritableThreadLocal<>();
tl.set("foo");
Thread b = new Thread(() -> {
String str = tl.get();
// 企圖拿到父線程放進(jìn)去的foo立哑,成功了夜惭!因?yàn)閠l是InheritableThreadLocal
System.out.println(str);
});
b.start();
// 子線程B啟動(dòng)后,父線程A休息3秒铛绰,確保子線程執(zhí)行完畢
TimeUnit.SECONDS.sleep(3);
// 清理
tl.remove();
可以看到诈茧,使用InheritableThreadLocal后,子線程能夠通過tl獲取父線程中set的值捂掰,實(shí)現(xiàn)了父線程向子線程傳遞線程私有數(shù)據(jù)的能力
這背后的原理是什么呢?
首先敢会,Thread類有兩個(gè)ThreadLocalMap類型的成員變量:threadLocals、inheritableThreadLocals这嚣,ThreadLocal使用的是threadLocals鸥昏,而InheritableThreadLocal使用的是inheritableThreadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
執(zhí)行tl.set("foo");時(shí),對應(yīng)的邏輯如下:
// java.lang.ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
由于是第一次執(zhí)行姐帚,map為空吏垮,走createMap的邏輯,而不同的tl實(shí)現(xiàn)類(ThreadLocal罐旗、InheritableThreadLocal)有著不同的創(chuàng)建map邏輯
ThreadLocal的如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
InheritableThreadLocal的如下:
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
很輕易能夠看出區(qū)別:ThreadLocal是給當(dāng)前線程的threadLocals變量賦值膳汪,而InheritableThreadLocal則是給當(dāng)前線程的inheritableThreadLocals變量賦值
其次,在創(chuàng)建線程實(shí)例九秀,即new Thread()時(shí)遗嗽,父線程使用自己的inheritableThreadLocals通過調(diào)用createInheritedMap方法來構(gòu)造子線程的ThreadLocalMap并賦值給子線程的inheritableThreadLocals
// java.lang.Thread#init
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 父線程的Entry
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
// 依次"拷貝"到子線程的Entry里
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) {
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++;
}
}
}
}
注意上邊標(biāo)注的一行:Object value = key.childValue(e.value);,方法childValue是一個(gè)可重載方法鼓蜒,對于InheritableThreadLocal而言痹换,直接返回了傳進(jìn)來的值:
protected T childValue(T parentValue) {
return parentValue;
}
這意味著是:淺拷貝征字,即默認(rèn)情況下,父線程傳遞給子線程的線程私有數(shù)據(jù)是一份淺拷貝晴音,若拷貝指向個(gè)不可變對象柔纵,這不是什么問題;若指向一個(gè)引用锤躁,那尤其需要注意搁料,因?yàn)椴辉儆芯€程封閉,父子線程中任意的變更均會(huì)影響到對方系羞。我們說它是可重載方法郭计,意味著若要實(shí)現(xiàn)"Deep Copy",需要自行繼承InheritableThreadLocal類并重寫childValue方法
小結(jié)一下解決方案背后的原理:使用InheritableThreadLocal椒振,會(huì)將線程私有數(shù)據(jù)存儲(chǔ)在inheritableThreadLocals指向的ThreadLocalMap中昭伸;在構(gòu)造子線程時(shí),將當(dāng)前線程inheritableThreadLocals里的數(shù)據(jù)(ThreadLocalMap)"拷貝"給子線程的ThreadLocalMap澎迎,子線程因此可以通過tl.get()取到數(shù)據(jù)庐杨,如此便實(shí)現(xiàn)了父線程向子線程傳遞線程私有數(shù)據(jù)
局限性二:父線程無法通過InheritableThreadLocal向池化的子線程(線程池)傳遞線程私有數(shù)據(jù)
在日常開發(fā)過程中,由于構(gòu)造與銷毀子線程開銷大夹供,因此每次在業(yè)務(wù)代碼中重新構(gòu)造一個(gè)子線程的方式并不常見灵份,更常見的方式是將線程池化(線程池),由線程池的調(diào)度策略決定線程們?nèi)绾螆?zhí)行提交給池中的任務(wù)哮洽,避免了重復(fù)構(gòu)造與銷毀線程的開銷填渠。上面我們提到,InheritableThreadLocal可以解決父線程向子線程傳遞線程私有數(shù)據(jù)的問題鸟辅,但一旦子線程池化之后氛什,InheritableThreadLocal也將不再起作用
ExecutorService executorService = Executors.newFixedThreadPool(1);
ThreadLocal<String> tl = new InheritableThreadLocal<>();
// ------------第一次調(diào)用 start -------------------
tl.set("foo");
executorService.execute(() -> {
String str = tl.get();
// 企圖拿到父線程放進(jìn)去的foo,成功了匪凉!
System.out.println(str);
});
// 父線程A休息3秒枪眉,確保提交給線程池的任務(wù)執(zhí)行完畢
TimeUnit.SECONDS.sleep(3);
// 清理
tl.remove();
// ------------第一次調(diào)用 end -------------------
// ------------第二次調(diào)用 start -------------------
tl.set("bar");
executorService.execute(() -> {
String str = tl.get();
// 企圖拿到父線程放進(jìn)去的bar,失敗了再层!
System.out.println(str);
});
// 父線程A休息3秒贸铜,確保提交給線程池的任務(wù)執(zhí)行完畢
TimeUnit.SECONDS.sleep(3);
// 清理
tl.remove();
// ------------第二次調(diào)用 end -------------------
如上,仍使用InheritableThreadLocal树绩,且構(gòu)造了只有1個(gè)線程的線程池,然后模擬兩次外部調(diào)用:第一次在父線程中將tl賦值為foo隐轩,然后子線程中獲取饺饭,使用完畢之后remove;第二次在父線程中將tl賦值為bar职车,然后子線程中獲取瘫俊,使用完畢之后remove
我們期望的結(jié)果是: foo bar
然而實(shí)際的結(jié)果卻是: foo foo
我們得到了與期望不符的結(jié)果鹊杖,原因很簡單,在局限性一解決方案原理中已經(jīng)闡述:子線程的ThreadLocalMap數(shù)據(jù)是在創(chuàng)建線程的那一刻從父線程中"拷貝"而來扛芽,此后再也沒有促使其變化的地方骂蓖,而子線程由于池化復(fù)用的緣故,ThreadLocalMap一直持有的是線程創(chuàng)建時(shí)刻的數(shù)據(jù)(即foo)川尖,此后無論進(jìn)行多少次方法調(diào)用登下,在(池化)子線程中通過tl.get()取出來的永遠(yuǎn)是foo
解決方案:使用transmittable-thread-local(alibaba)
暫不提開源框架是如何解決這個(gè)問題的,我們先自己推導(dǎo)叮喳,進(jìn)而得出與開源框架一致的結(jié)論被芳,幫助大家理好地理解解決方案背后的原理
仔細(xì)推敲琢磨一下,我們需要的并不是創(chuàng)建線程的那一刻父線程的ThreadLocal值馍悟,而是提交任務(wù)時(shí)父線程的ThreadLocal值畔濒。或者換種表述方式锣咒,需要把任務(wù)提交給線程池時(shí)的ThreadLocal值傳遞到任務(wù)執(zhí)行時(shí)
具體思路是:父線程把任務(wù)提交給線程池時(shí)一同附上此刻自己的ThreadLocalMap侵状,包裝在task里,待線程池中某個(gè)線程執(zhí)行到該任務(wù)時(shí)毅整,用task里的ThreadLocalMap賦蓋當(dāng)前線程ThreadLocalMap趣兄,這樣就完成了父線程向池化的子線程傳遞線程私有數(shù)據(jù)的目標(biāo)。為了避免數(shù)據(jù)污染毛嫉,待任務(wù)執(zhí)行完后诽俯,線程歸還回線程池之前,還需要還原ThreadLocalMap承粤,如下示:
上面的步驟一共有6步暴区,其中2、4辛臊、6是線程池本身的提供的能力仙粱,不需要改動(dòng),只有1彻舰、3伐割、5有所不同,我們逐一剖析
第1步疑問:提交Task的時(shí)候如何將ThreadLocalMap一同提交上去刃唤?此處難點(diǎn)在于如何獲取當(dāng)前線程的ThreadLocalMap
ThreadLocalMap是線程私有變量隔心,只會(huì)被ThreadLocal維護(hù),對于外部類而言是不可見的尚胞,因此要操作ThreadLocalMap就得通過ThreadLocal(操作ThreadLocal本質(zhì)上是在操作當(dāng)前線程的ThreadLocalMap)
首先自定義Task硬霍,用于包裝維護(hù)父線程ThreadLocalMap
public class MyTask implements Runnable {
// key是ThreadLocal,value是對應(yīng)父線程的線程私有數(shù)據(jù)
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(ThreadLocal<Object>... threadLocals) {
this.threadLocalValues = new HashMap<>(threadLocals.length);
capture(threadLocals);
}
private void capture(ThreadLocal<Object>[] threadLocals) {
for (ThreadLocal<Object> threadLocal : threadLocals) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
}
@Override
public void run() {
// todo
}
}
使用時(shí):
ThreadLocal<Object> tl1 = new ThreadLocal<>();
tl1.set("111111");
ThreadLocal<Object> tl2 = new ThreadLocal<>();
tl2.set("222222");
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 將父線程的ThreadLocal傳進(jìn)去笼裳,就能取到相應(yīng)的值
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
如此這般唯卖,提交到線程池的MyTask就包含了父線程的ThreadLoalMap數(shù)據(jù)粱玲。我們把"拷貝"父線程TheadLocalMap的行為稱為capture(拍照),一個(gè)很生動(dòng)形象的詞:將父線程提交任務(wù)時(shí)刻的ThreadLocal值拍個(gè)快照并保存起來拜轨,后續(xù)使用
第3步疑問:如何用父線程的ThreadLocalMap覆蓋當(dāng)前執(zhí)行任務(wù)線程的ThreadLocalMap抽减?
我們可以想像到,代碼正執(zhí)行到MyTask#run()方法橄碾,在該方法內(nèi)部卵沉,能感知的上下文環(huán)境是正執(zhí)行該方法的線程,以及MyTask維護(hù)的threadLocalValues(快照)堪嫂,除此之外偎箫,它獲取不了任何外界信息–> 這稱之為線程封閉
因此可以比較自然地推理出,是要用MyTask的threadLocalValues(快照)去覆蓋當(dāng)前線程的ThreadLocalMap皆串。我們稱這個(gè)動(dòng)作為replay(重放)
public class MyTask implements Runnable {
// ...(省略)
@Override
public void run() {
replay();
// do biz
}
// 重放淹办,用快照去覆蓋當(dāng)前線程的ThreadLocalMap
private void replay() {
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
第5步疑問:如何把當(dāng)前線程的ThreadLocalMap還原?
Task業(yè)務(wù)邏輯執(zhí)行完之后恶复,毫無疑問需要將ThreadLocalMap還原怜森,否則可能產(chǎn)生數(shù)據(jù)污染的風(fēng)險(xiǎn)
能夠還原的前提是,用快照去覆蓋當(dāng)前線程的ThreadLocalMap之前谤牡,先將當(dāng)前的ThreadLocal值保存起來副硅,因此,修改代碼如下:
private Object replay() {
// 保存當(dāng)前的ThreadLocal值
Map<ThreadLocal<Object>, Object> backup = new HashMap<>();
for (ThreadLocal<Object> threadLocal : threadLocalValues.keySet()) {
backup.put(threadLocal, threadLocal.get());
}
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
return backup;
}
業(yè)務(wù)代碼執(zhí)行完之后翅萤,進(jìn)行restore(還原):
public class MyTask implements Runnable {
// ...(省略)
@Override
public void run() {
Object backup = replay();
try {
// do biz
} finally {
restore(backup);
}
}
// 還原
private void restore(Object obj) {
Map<ThreadLocal<Object>, Object> backup = (Map<ThreadLocal<Object>, Object>) obj;
for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
經(jīng)過上面的1恐疲、3、5步分析套么,我們已經(jīng)把所有關(guān)鍵問題都分析完畢培己,因此,做一個(gè)小實(shí)驗(yàn)看看結(jié)果胚泌,測試代碼:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ThreadLocal<Object> tl1 = new ThreadLocal<>();
ThreadLocal<Object> tl2 = new ThreadLocal<>();
// ------------第一次調(diào)用 start -------------------
tl1.set("1111");
tl2.set("2222");
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
// ------------第一次調(diào)用 end -------------------
// ------------第二次調(diào)用 start -------------------
tl1.set("3333");
tl2.set("4444");
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
// ------------第二次調(diào)用 end -------------------
public void run() {
Object backup = replay();
try {
// do biz
doBiz();
} finally {
restore(backup);
}
}
private void doBiz() {
// 打印父線程提交任務(wù)時(shí)的ThreadLocal值
Set<ThreadLocal<Object>> threadLocals = threadLocalValues.keySet();
for (ThreadLocal<Object> threadLocal : threadLocals) {
System.out.println(threadLocal.get());
}
}
證明是可行的
但是這樣還不夠省咨,稍稍有些經(jīng)驗(yàn)的朋友應(yīng)該能感受到,上面的寫法僅是達(dá)到了"可用"的程度玷室,離"好用"還有一段距離:業(yè)務(wù)在向線程池提交任務(wù)的時(shí)候零蓉,需要每次都構(gòu)建自定義的Task,并將ThreadLocal的引用傳入穷缤,而且Task糅進(jìn)了TheadLocal管理的邏輯敌蜂,這樣其實(shí)形成了"業(yè)務(wù)侵入性",沒有做到與業(yè)務(wù)解耦津肛,這樣的代碼是不可維護(hù)的
TheadLocal管理的邏輯章喉,業(yè)務(wù)代碼不應(yīng)該關(guān)心,因此為了與業(yè)務(wù)解耦,容易想到的一種解決方案是:代理囊陡。通過代理可以實(shí)現(xiàn)對被代理類的邏輯增強(qiáng),并將通用的非業(yè)務(wù)邏輯與業(yè)務(wù)代碼隔離開來(設(shè)計(jì)模式縈繞心頭)
提及代理掀亥,熟悉的同學(xué)對于套路了然于心:定義代理類撞反,實(shí)現(xiàn)與被代理類相同的接口,并在內(nèi)部維護(hù)被代理類的實(shí)例搪花,之后就可以對被代理的方法實(shí)現(xiàn)額外邏輯遏片,來增強(qiáng)被代理類,代碼變更后如下:
public class MyTask implements Runnable {
private Runnable task;
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(Runnable task, ThreadLocal<Object>... threadLocals) {
this.task = task;
this.threadLocalValues = new HashMap<>(threadLocals.length);
capture(threadLocals);
}
// ...(省略)
@Override
public void run() {
Object backup = replay(); // 增強(qiáng)的邏輯
try {
task.run(); // 執(zhí)行業(yè)務(wù)代碼
} finally {
restore(backup); // 增強(qiáng)的邏輯
}
}
}
使用方式
executorService.execute(new MyTask(() -> {
// do biz
}, tl1, tl2));
這樣提交任務(wù)時(shí)撮竿,業(yè)務(wù)開發(fā)只需要關(guān)心()->{// do biz} 業(yè)務(wù)邏輯吮便,而不需要關(guān)心MyTask代理類如何實(shí)現(xiàn)的增強(qiáng)邏輯,做到了與業(yè)務(wù)代碼的解耦
但是幢踏,上面的代碼書寫起來仍然有些別扭:需要業(yè)務(wù)方主動(dòng)傳遞tl1髓需、tl2,說明業(yè)務(wù)方仍然需要有一定程度的參與房蝉,那能不能更徹底一些僚匆,連傳遞tl1、tl2的行為都省去呢搭幻?答案是肯定的
如果要避免父線程"主動(dòng)"傳遞ThreadLocal的行為咧擂,那么就必須要知道父線程往ThreadLocalMap放數(shù)據(jù)這件事,并且在事件發(fā)生的時(shí)候?qū)hreadLocal引用保存下來檀蹋;同時(shí)松申,如果父線程調(diào)用ThreadLocal#remove方法清除數(shù)據(jù),也需要將保存下來的ThreadLocal引用一同清除掉
因此俯逾,需要自定義ThreadLocal類:
public class MyThreadLocal<T> extends ThreadLocal<T> {
private static MyThreadLocal<HashSet<MyThreadLocal<Object>>> holder =
new MyThreadLocal<HashSet<MyThreadLocal<Object>>>() {
@Override
protected HashSet<MyThreadLocal<Object>> initialValue() {
return new HashSet<>();
}
};
// 重寫set方法
@Override
public void set(T value) {
super.set(value);
addThisToHolder();
}
// 將ThreadLocal引用記錄下來
private void addThisToHolder() {
if (!holder.get().contains(this)) {
holder.get().add((MyThreadLocal<Object>) this);
}
}
// 重寫remove方法
@Override
public void remove() {
super.remove();
removeThisFromHolder();
}
// 將ThreadLocal引用記錄移除
private void removeThisFromHolder() {
holder.get().remove(this);
}
}
重寫set方法贸桶,在ThreadLocal#set方法執(zhí)行時(shí)將ThreadLocal引用記錄下來,保存在類成員變量holder中纱昧;重寫remove方法刨啸,在ThreadLocal#remove方法執(zhí)行時(shí)將ThreadLocal引用一并移除
接下來,父線程向線程池提交Task识脆,不再傳遞ThreadLocal的引用设联,那又怎么完成capture的動(dòng)作呢?(如果看官們忘記了灼捂,請往上翻离例,前文在執(zhí)行capture方法時(shí),入?yún)⑹荰hreadLocal引用數(shù)組)
既然我們將ThreadLocal的引用保存在MyThreadLocal#holder這個(gè)靜態(tài)變量中悉稠,那我們想辦法暴露holder宫蛆,不就可以得到capture需要的入?yún)⒘藛幔?/p>
這種思路誠然是可行的,但從面向?qū)ο笤O(shè)計(jì)角度而言卻不是最優(yōu)的:holder是MyThreadLocal的靜態(tài)成員變量,維護(hù)的數(shù)據(jù)是ThreadLocal集合耀盗,它不應(yīng)該將自身數(shù)據(jù)暴露出去想虎,而是遵循高內(nèi)聚的設(shè)計(jì)原則,提供數(shù)據(jù)操作的能力(方法)叛拷,例如提供capture的能力舌厨,但操作本身需要維護(hù)在類內(nèi)部。因此應(yīng)該是MyThreadLocal提供capture的能力忿薇,然后由需求方(MyTask)進(jìn)行調(diào)用裙椭。代碼如下:
public class MyThreadLocal<T> extends ThreadLocal<T> {
// ...(省略)
public static class DataTransmit {
public static Map<ThreadLocal<Object>, Object> capture() {
Map<ThreadLocal<Object>, Object> threadLocalValues = new HashMap<>();
for (MyThreadLocal<Object> threadLocal : holder.get()) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
return threadLocalValues;
}
}
}
此處,我在MyThreadLocal中定義了一個(gè)內(nèi)部類DataTransmit署浩,用于ThreadLocal的數(shù)據(jù)傳輸揉燃,與MyThreadLocal本身提供的能力相隔離(SRP原則)。然后筋栋,將原先定義于MyTask的capture方法搬到了DataTransmit類內(nèi)炊汤,提供capture的能力。此時(shí)弊攘,MyTask構(gòu)造函數(shù)代碼如下:
public MyTask(Runnable task) {
this.task = task;
threadLocalValues = MyThreadLocal.DataTransmit.capture();
}
我們將capture方法搬走之后婿崭,仍然還有replay、restore方法肴颊,仔細(xì)思考氓栈,它們都是對ThreadLocal的操作,放在MyThreadLocal.DataTransmit中更合適一些婿着,使得內(nèi)聚度更高
最終授瘦,MyThreadLocal代碼如下
public class MyThreadLocal<T> extends ThreadLocal<T> {
private static MyThreadLocal<HashSet<MyThreadLocal<Object>>> holder =
new MyThreadLocal<HashSet<MyThreadLocal<Object>>>() {
@Override
protected HashSet<MyThreadLocal<Object>> initialValue() {
return new HashSet<>();
}
};
@Override
public void set(T value) {
super.set(value);
addThisToHolder();
}
private void addThisToHolder() {
if (!holder.get().contains(this)) {
holder.get().add((MyThreadLocal<Object>) this);
}
}
@Override
public void remove() {
super.remove();
removeThisFromHolder();
}
private void removeThisFromHolder() {
holder.get().remove(this);
}
public static class DataTransmit {
public static Map<ThreadLocal<Object>, Object> capture() {
Map<ThreadLocal<Object>, Object> threadLocalValues = new HashMap<>();
for (MyThreadLocal<Object> threadLocal : holder.get()) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
return threadLocalValues;
}
// 重放,用快照去覆蓋當(dāng)前線程的ThreadLocalMap
public static Object replay(Object obj) {
Map<ThreadLocal<Object>, Object> threadLocalValues = (Map<ThreadLocal<Object>, Object>)obj;
Map<ThreadLocal<Object>, Object> backup = new HashMap<>();
for (ThreadLocal<Object> threadLocal : threadLocalValues.keySet()) {
backup.put(threadLocal, threadLocal.get());
}
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
return backup;
}
public static void restore(Object obj) {
Map<ThreadLocal<Object>, Object> backup = (Map<ThreadLocal<Object>, Object>) obj;
for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
}
MyTask經(jīng)過精簡后的代碼如下:
public class MyTask implements Runnable {
private Runnable task;
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(Runnable task) {
this.task = task;
threadLocalValues = MyThreadLocal.DataTransmit.capture();
}
@Override
public void run() {
Object backup = MyThreadLocal.DataTransmit.replay(threadLocalValues);
try {
task.run();
} finally {
MyThreadLocal.DataTransmit.restore(backup);
}
}
}
使用代碼如下:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ThreadLocal<Object> tl1 = new MyThreadLocal<>();
ThreadLocal<Object> tl2 = new MyThreadLocal<>();
// ------------第一次調(diào)用 start -------------------
tl1.set("1111");
tl2.set("2222");
executorService.execute(new MyTask(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
}));
tl1.remove();
tl2.remove();
// ------------第一次調(diào)用 end -------------------
// ------------第二次調(diào)用 start -------------------
tl1.set("3333");
tl2.set("4444");
executorService.execute(new MyTask(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
}));
tl1.remove();
tl2.remove();
// ------------第二次調(diào)用 end -------------------
這樣業(yè)務(wù)代碼就簡潔了竟宋。
如果還是覺得上述使用姿勢有點(diǎn)麻煩:每次提交任務(wù)提完,都要構(gòu)造一個(gè)MyTask,能不能連這一步都省去丘侠,變成跟規(guī)常的寫法一致呢徒欣?
executorService.execute(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
});
答案是肯定的,首先需要明確的一點(diǎn)是蜗字,要增強(qiáng)就意味著要代理打肝;接著稍稍轉(zhuǎn)變一下思路:既然不能對Task進(jìn)行代理,那么我們對線程池進(jìn)行代理增強(qiáng)挪捕,是不是也可以達(dá)到同樣的效果粗梭?
代理套路相信大家很熟悉了:
public class MyExecutorService implements ExecutorService {
private ExecutorService executorService;
public MyExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public void execute(Runnable command) {
executorService.execute(new MyTask(command)); // 在內(nèi)部進(jìn)行Task的代理
}
// ...(省略)
}
使用:
ExecutorService executorService = new MyExecutorService(Executors.newFixedThreadPool(1));
這樣,業(yè)務(wù)代碼又精簡了一步:只需要對線程池進(jìn)行代理一次即可级零,后續(xù)提交任務(wù)不需要手動(dòng)構(gòu)建MyTask
最后断医,如果連對線程池的代理都感覺稍顯麻煩,只想使用原生的姿勢,那就要請出尚方寶劍:Java Agent鉴嗤,在應(yīng)用啟動(dòng)之初對JDK代碼進(jìn)行修改斩启,植入代理邏輯,如此業(yè)務(wù)代碼不需要進(jìn)行其它改動(dòng)醉锅,就享有增強(qiáng)后的ExecutorService以及Task