ThreadLocal
在我之前的文章中介紹過什么是ThreadLocal,但是我現(xiàn)在有另一個需求,我想在子線程中獲取到父線程中ThreadLocal的數(shù)據(jù),例如下面的代碼示例:
public class App11 {
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
new Thread(()->{
Integer data = threadLocal.get();
System.out.printf("%s 獲取到的值:%d\n",Thread.currentThread().getName(),data);
}).start();
TimeUnit.SECONDS.sleep(1L);
System.out.printf("%s 獲取到的值:%d\n",Thread.currentThread().getName(),threadLocal.get());
}
}
如果了解ThreadLocal的話可以知道最后的打印結(jié)果如下:
Thread-0 獲取到的值:null
main 獲取到的值:1
如果我想在子線程中獲取到父線程放入的值,我該怎么辦呢?最簡單的方法就是將該值傳入到子線程,但是這種方式比較麻煩,有沒有更簡便的方式呢?
InheritableThreadLocal
對于上面所說的情況,我們只需要使用InheritableThreadLocal就能解決,修改代碼如下:
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
我這里講ThreadLocal直接改成InheritableThreadLocal,再次運行代碼結(jié)果如下:
Thread-0 獲取到的值:1
main 獲取到的值:1
實現(xiàn)原理
通過上面的方式我們很簡單的就實現(xiàn)了子線程獲取到父線程中的值,那么InheritableThreadLocal是如何做到的呢?我們先看這個類的定義:
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);
}
}
從代碼結(jié)構(gòu)上我們很容易看出它是ThreadLocal的一個子類,同時它重寫了ThreadLocal中的三個方法??吹竭@里的實現(xiàn)你一定在想為什么要重寫這三個方法呢?這里我先解釋一下為什么要重寫getMap方法和createMap方法。
在講ThreadLocal是的時候我們見過,ThreadLocal是通過在Thread中保存ThreadLocal與數(shù)據(jù)的映射關(guān)系存在Thread的變量threadLocals中來實現(xiàn)的,而重寫getMap它返回的是inheritableThreadLocals。簡單的說就是InheritableThreadLocal是通過在Thread中保存InheritableThreadLocal與數(shù)據(jù)的映射關(guān)系存在Thread的變量inheritableThreadLocals中來實現(xiàn)的,這樣做當(dāng)我們在操作inheritableThreadLocals時將不會影響到ThreadLocal中的數(shù)據(jù)。而重寫createMap的原因與getMap類似。
而childValue該方法是在父線程創(chuàng)建子線程,向子線程復(fù)制InheritableThreadLocal變量時使用。而InheritableThreadLocal中的實現(xiàn)對于向子線程復(fù)制值是并沒有做任何改變,如果在上面的例子中,我們想在子線程獲取到父線程的值時乘以10,我們可以繼承InheritableThreadLocal并重寫它的childValue方法即可:
public class App11 {
//修改成我們自己的InheritableThreadLocal
public static ThreadLocal<Integer> threadLocal = new MyInheritableThreadLocal();
public static void main(String[] args) throws InterruptedException {
threadLocal.set(1);
new Thread(()->{
Integer data = threadLocal.get();
System.out.printf("%s 獲取到的值:%d\n",Thread.currentThread().getName(),data);
}).start();
TimeUnit.SECONDS.sleep(1L);
System.out.printf("%s 獲取到的值:%d\n",Thread.currentThread().getName(),threadLocal.get());
}
}
//自定義的InheritableThreadLocal
class MyInheritableThreadLocal extends InheritableThreadLocal<Integer>{
@Override
protected Integer childValue(Integer parentValue) {
return parentValue * 10;
}
}
最后的打印結(jié)果如下:
Thread-0 獲取到的值:10
main 獲取到的值:1
子線程是如何獲取到父線程中的數(shù)據(jù)
在創(chuàng)建線程的構(gòu)造方法中會調(diào)用一個init方法,應(yīng)為該方法較長我截取了部分關(guān)鍵代碼如下:

從上面代碼可以知道,子線程中的inheritableThreadLocals是通過下面的代碼獲取的:
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
我們直接繼續(xù)跟蹤ThreadLocal.createInheritedMap的實現(xiàn)直接就是調(diào)用ThreadLocalMap私有的有參構(gòu)造方法,該方法的內(nèi)部實現(xiàn)如下:

該方法的實現(xiàn)就是將父線程中inheritableThreadLocals的數(shù)據(jù)復(fù)制到子線程的inheritableThreadLocals中,從而實現(xiàn)了我們在子線程中可以獲取到父線程中的值。
總結(jié)
如果明白ThreadLocal是如何實現(xiàn)的,再來理解InheritableThreadLocal的實現(xiàn)原理就很簡單的了。簡單的說就是ThreadLocal是通過將ThreadLocal與數(shù)據(jù)的映射關(guān)系存在Thread的變量threadLocals中來實現(xiàn)的,而InheritableThreadLocal則是將這種關(guān)系存在inheritableThreadLocals中,而在子線程創(chuàng)建時它會將父線程中inheritableThreadLocals的值復(fù)制到子線程的inheritableThreadLocals。
但是InheritableThreadLocal也有一個限制,在實際的開發(fā)中我們很少直接創(chuàng)建線程,一般都是通過線程池的方式來獲取線程。這樣也導(dǎo)致了InheritableThreadLocal在線程池中無法實現(xiàn)這種效果,因為線程池中的線程會重復(fù)利用。如果有這方面的需求,我們可以選擇使用阿里開源的TransmittableThreadLocal來實現(xiàn)。