前言
介紹 InheritableThreadLocal 之前轮傍,假設對 ThreadLocal 已經有了一定的理解宰译,比如基本概念,原理充尉。這里再復習下 ThreadLocal 的原理留潦,因為會對 InheritableThreadLocal 的理解 有重大的幫助:
- 每個線程都有一個 ThreadLocalMap 類型的 threadLocals 屬性只盹。
- ThreadLocalMap 類相當于一個Map,key 是 ThreadLocal 本身兔院,value 就是我們的值殖卑。
- 當我們執(zhí)行 threadLocal.set(new Integer(123)); ,我們就會在這個線程中的 threadLocals 屬性中放入一個鍵值對秆乳,key是這個threadLocal.set(new Integer(123)); 的 threadlocal懦鼠,value 就是值。
- 當我們通過 threadlocal.get() 方法的時候屹堰,首先會根據這個線程得到這個線程的 threadLocals 屬性肛冶,然后由于這個屬性放的是鍵值對,我們就可以根據鍵 threadlocal 拿到值扯键。 注意睦袖,這時候這個鍵 threadlocal 和 我們 set 方法的時候的那個鍵 threadlocal 是一樣的,所以我們能夠拿到相同的值荣刑。
InheritableThreadLocal 概念
從上面的介紹我們可以知道馅笙,我們其實是根據 Thread.currentThread(),拿到該線程的 threadlocals厉亏,從而進一步得到我們之前預先 set 好的值董习。那么如果我們新開一個線程,這個時候爱只,由于 Thread.currentThread() 已經變了皿淋,從而導致獲得的 threadlocals 不一樣,我們之前并沒有在這個新的線程的 threadlocals 中放入值恬试,那么我就再通過 threadlocal.get()方法 是不可能拿到值的窝趣。例如如下代碼:
public class Test {
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String args[]){
threadLocal.set(new Integer(123));
Thread thread = new MyThread();
thread.start();
System.out.println("main = " + threadLocal.get());
}
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread = " + threadLocal.get());
}
}
}
輸出是:
main = 123
MyThread = null
那么這個時候怎么解決? InheritableThreadLocal 就可以解決這個問題训柴。 先看一個官方對它的介紹:
* This class extends <tt>ThreadLocal</tt> to provide inheritance of values
* from parent thread to child thread: when a child thread is created, the
* child receives initial values for all inheritable thread-local variables
* for which the parent has values. Normally the child's values will be
* identical to the parent's; however, the child's value can be made an
* arbitrary function of the parent's by overriding the <tt>childValue</tt>
* method in this class.
也就是說哑舒,我們把上面的
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
改成
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
再運行,就會有結果:
main = 123
MyThread = 123
也就是子線程或者說新開的線程拿到了該值幻馁。那么洗鸵,這個究竟是怎么實現的呢,key 都變了宣赔,為什么還可以拿到呢预麸?
InheritableThreadLocal 原理
我們可以首先可以瀏覽下 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);
}
}
其實就是重寫了3個方法。
首先儒将,當我們調用 get 方法的時候吏祸,由于子類沒有重寫,所以我們調用了父類的 get 方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
這里會有一個Thread.currentThread() 钩蚊, getMap(t) 方法贡翘,所以就會得到這個線程 threadlocals。 但是砰逻,由于子類 InheritableThreadLocal 重寫了 getMap()方法鸣驱,再看上述代碼,我們可以看到:
其實不是得到 threadlocals蝠咆,而是得到 inheritableThreadLocals踊东。 inheritableThreadLocals 之前一直沒提及過北滥,其實它也是 Thread 類的一個 ThreadLocalMap 類型的 屬性,如下 Thread 類的部分代碼:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
那么闸翅,這里看 InheritableThreadLocal 重寫的方法再芋,感覺 inheritableThreadLocals 和 threadLocals 幾乎是一模一樣的作用,只是換了個名字而且坚冀,那么究竟 為什么在新的 線程中 通過 threadlocal.get() 方法還能得到值呢济赎?
這時候要注意 childValue 方法,我們可以看下它的官方說明:
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
這個時候记某,你明白了司训,是不是在 創(chuàng)建線程的時候做了手腳,做了一些值的傳遞液南,或者這里利用上了 inheritableThreadLocals 之類的壳猜。
其實,是的:
關鍵在于 Thread thread = new MyThread(); 這不是一個簡簡單單的 new 操作滑凉。當我們 new 一個 線程的時候:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
然后:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
然后:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
......
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
......
}
這時候有一句 'ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);' 蓖谢,然后
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
繼續(xù)跟蹤:
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
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++;
}
}
}
}
當我們創(chuàng)建一個新的線程X的時候,X線程就會有 ThreadLocalMap 類型的 inheritableThreadLocals 譬涡,因為它是 Thread 類的一個屬性闪幽。
然后:
先得到當前線程存儲的這些值,例如 Entry[] parentTable = parentMap.table; 涡匀。再通過一個 for 循環(huán)盯腌,不斷的把當前線程的這些值復制到我們新創(chuàng)建的線程X 的inheritableThreadLocals 中。就這樣陨瘩,就ok了腕够。
那么這樣會有一個什么結果呢?
結果就是我們創(chuàng)建的新線程X 的inheritableThreadLocals 變量中已經有了值了舌劳。那么我在新的線程X中調用threadlocal.get() 方法帚湘,首先會得到新線程X 的 inheritableThreadLocals,然后甚淡,再根據threadlocal.get()中的 threadlocal大诸,就能夠得到這個值。
這樣就避免了 新線程中得到的 threadlocals 沒有東西贯卦。之前就是因為沒有東西资柔,所以才拿不到值。
所以說 整個 InheritableThreadLocal 的實現原理就是這樣的撵割。
總結
首先要理解 為什么 在 新線程中得不到值贿堰,是因為我們其實是根據 Thread.currentThread(),拿到該線程的 threadlocals啡彬,從而進一步得到我們之前預先 set 好的值羹与。那么如果我們新開一個線程故硅,這個時候,由于 Thread.currentThread() 已經變了纵搁,從而導致獲得的 threadlocals 不一樣契吉,我們之前并沒有在這個新的線程的 threadlocals 中放入值,那么我就再通過 threadlocal.get()方法 是不可能拿到值的诡渴。
那么解決辦法就是 我們在新線程中,要把父線程的 threadlocals 的值 給復制到 新線程中的 threadlocals 中來菲语。這樣妄辩,我們在新線程中得到的 threadlocals 才會有東西,再通過 threadlocal.get() 中的 threadlocal山上,就會得到值眼耀。