Java 多線程:InheritableThreadLocal 實現原理

前言

介紹 InheritableThreadLocal 之前轮傍,假設對 ThreadLocal 已經有了一定的理解宰译,比如基本概念,原理充尉。這里再復習下 ThreadLocal 的原理留潦,因為會對 InheritableThreadLocal 的理解 有重大的幫助:

  1. 每個線程都有一個 ThreadLocalMap 類型的 threadLocals 屬性只盹。
  2. ThreadLocalMap 類相當于一個Map,key 是 ThreadLocal 本身兔院,value 就是我們的值殖卑。
  3. 當我們執(zhí)行 threadLocal.set(new Integer(123)); ,我們就會在這個線程中的 threadLocals 屬性中放入一個鍵值對秆乳,key是這個threadLocal.set(new Integer(123)); 的 threadlocal懦鼠,value 就是值。
  4. 當我們通過 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 的實現原理就是這樣的撵割。

總結

  1. 首先要理解 為什么 在 新線程中得不到值贿堰,是因為我們其實是根據 Thread.currentThread(),拿到該線程的 threadlocals啡彬,從而進一步得到我們之前預先 set 好的值羹与。那么如果我們新開一個線程故硅,這個時候,由于 Thread.currentThread() 已經變了纵搁,從而導致獲得的 threadlocals 不一樣契吉,我們之前并沒有在這個新的線程的 threadlocals 中放入值,那么我就再通過 threadlocal.get()方法 是不可能拿到值的诡渴。

  2. 那么解決辦法就是 我們在新線程中,要把父線程的 threadlocals 的值 給復制到 新線程中的 threadlocals 中來菲语。這樣妄辩,我們在新線程中得到的 threadlocals 才會有東西,再通過 threadlocal.get() 中的 threadlocal山上,就會得到值眼耀。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市佩憾,隨后出現的幾起案子哮伟,更是在濱河造成了極大的恐慌,老刑警劉巖妄帘,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞黄,死亡現場離奇詭異,居然都是意外死亡抡驼,警方通過查閱死者的電腦和手機鬼廓,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來致盟,“玉大人碎税,你說我怎么就攤上這事×笪” “怎么了雷蹂?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杯道。 經常有香客問我匪煌,道長,這世上最難降的妖魔是什么党巾? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任虐杯,我火速辦了婚禮,結果婚禮上昧港,老公的妹妹穿的比我還像新娘擎椰。我一直安慰自己,他們只是感情好创肥,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布达舒。 她就那樣靜靜地躺著值朋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巩搏。 梳的紋絲不亂的頭發(fā)上昨登,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音贯底,去河邊找鬼丰辣。 笑死,一個胖子當著我的面吹牛禽捆,可吹牛的內容都是我干的笙什。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼胚想,長吁一口氣:“原來是場噩夢啊……” “哼琐凭!你這毒婦竟也來了?” 一聲冷哼從身側響起浊服,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤统屈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牙躺,有當地人在樹林里發(fā)現了一具尸體愁憔,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年孽拷,在試婚紗的時候發(fā)現自己被綠了惩淳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡乓搬,死狀恐怖思犁,靈堂內的尸體忽然破棺而出进肯,到底是詐尸還是另有隱情,我是刑警寧澤江掩,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站环形,受9級特大地震影響策泣,放射性物質發(fā)生泄漏。R本人自食惡果不足惜抬吟,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望火本。 院中可真熱鬧聪建,春花似錦、人聲如沸茫陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽簿盅。三九已至挥下,卻和暖如春桨醋,著一層夾襖步出監(jiān)牢的瞬間棚瘟,已是汗流浹背讨盒。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工步责, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔓肯。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像蔗包,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子调限,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容