InheritableThreadLocal源碼解析,子線程如何獲取父線程的本地變量侥衬?

一诗祸、前言

日常工作中,經(jīng)常使用ThreadLocal來避免線程并發(fā)問題轴总,每個(gè)線程訪問自己的本地變量直颅,沒有競(jìng)爭(zhēng),沒有鎖肘习,非常高效〖食耍現(xiàn)在有一個(gè)業(yè)務(wù)場(chǎng)景,需要?jiǎng)?chuàng)建一些子線程來執(zhí)行任務(wù)漂佩,父線程中設(shè)置了ThreadLocal的值,想在子線程中獲取罪塔,能獲取到嗎投蝉?答案是:不能。

ThreadLocalTest

了解ThreadLocal的原理征堪,這個(gè)問題就很弱智瘩缆,用腳后跟想,父線程中set佃蚜,那么這個(gè)存放值的ThreadLocalMap就在父線程內(nèi)庸娱,子線程的threadLocals是個(gè)null,怎么可能從子線程get到父線程set的值呢谐算?

但是需求就要這樣熟尉,該如何實(shí)現(xiàn)?將父線程的ThreadLocalMap復(fù)制一份給子線程洲脂?沒錯(cuò)斤儿,java官方也是這么想的!

二、InheritableThreadLocal

1往果、使用方式

java 官方提供了一個(gè)類InheritableThreadLocal疆液,使用方式上和ThreadLocal完全一樣,就是類名不一樣陕贮。將ThreadLocal替換為InheritableThreadLocal堕油,就可以從子線程get到父線程set的值了。

InheritableThreadLocalTest

2肮之、繼承關(guān)系

InheritableThreadLocal是如何做到的呢掉缺?源碼底下見真知:

package java.lang;
/**
 * @author  Josh Bloch and Doug Lea
 * @see     ThreadLocal
 * @since   1.2
 */
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繼承自ThreadLocal,重寫了三個(gè)方法childValue局骤、getMap攀圈、createMap,用到Thread的一個(gè)變量inheritableThreadLocals峦甩。那就是InheritableThreadLocal初始化的ThreadLocalMap賦值給t.inheritableThreadLocals赘来,setget也是操作t.inheritableThreadLocals

public class Thread implements Runnable {
    ... ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ... ...
}

3凯傲、復(fù)制原理

那是如何將父線程的map復(fù)制給子線程的呢犬辰?

追溯到Thread初始化,會(huì)調(diào)用一個(gè)init()冰单,init初始化的東西較多幌缝,直接看重點(diǎn):

inheritableThreadLocals復(fù)制機(jī)制

真相了,創(chuàng)建子線程時(shí)诫欠,默認(rèn)inheritThreadLocals=true涵卵,父線程即當(dāng)前線程的inheritableThreadLocals!=null,則將父線程的inheritableThreadLocals復(fù)制給子線程荒叼。

// java.lang.ThreadLocal#createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    // 遍歷復(fù)制
    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) {
                // InheritableThreadLocal重寫了childValue
                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++;
            }
        }
    }
}

三轿偎、childValue的用意

ThreadLocalchildValue沒有給出具體實(shí)現(xiàn),而在InheritableThreadLocal中也只是簡單實(shí)現(xiàn)了下被廓。

//java.lang.ThreadLocal#childValue
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}
//java.lang.InheritableThreadLocal#childValue
protected T childValue(T parentValue) {
    return parentValue;
}

從父線程復(fù)制ThreaLocalMap到子線程時(shí)坏晦,值從childValue函數(shù)過了一遍再賦值給Entry,是何意圖嫁乘?關(guān)鍵是InheritableThreadLocal也沒做什么昆婿,但是不難猜出,ThreadLocal留了一個(gè)childValue就是讓InheritableThreadLocal實(shí)現(xiàn)的蜓斧,雖然InheritableThreadLocal沒做什么仓蛆,但是使用者可以繼承InheritableThreadLocal重寫childValue,對(duì)value做特殊處理法精。為什么可能要對(duì)value做特殊處理呢多律?

比如痴突,設(shè)置的值是一個(gè)自定義的引用類型,那么從父線程復(fù)制到多個(gè)子線程的值就存在并發(fā)問題(值傳遞狼荞,地址值是共享的)辽装,所以復(fù)制的時(shí)候要保證復(fù)制給每個(gè)子線程的地址值不一樣,繼承InheritableThreadLocal實(shí)現(xiàn)childValue的深拷貝相味,定制化一個(gè)自己的InheritableThreadLocal

public class MyInheritableThreadLocal<T> extends InheritableThreadLocal<T>{
    protected T childValue(T parentValue) {
        System.out.println("MyInheritableThreadLocal拾积。。丰涉。");
        // 深拷貝
        Gson gson = new Gson();
        String s = gson.toJson(parentValue);
        return (T)gson.fromJson(s, parentValue.getClass());
    }
}
public class InheritableThreadLocalTest {
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal<Stu> itl = new MyInheritableThreadLocal<Stu>();
        itl.set(new Stu());
        System.out.println("父線程:" + itl.get().toString());
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程1:" + itl.get().toString());
            }
        });
        thread1.start();
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程2:" + itl.get().toString());
            }
        });
        thread2.start();
    }

    static class Stu {
        private String name = "xxx";
    }
}

// 控制臺(tái)打印
父線程:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@49476842
MyInheritableThreadLocal拓巧。。一死。
子線程1:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@7446b2ac
MyInheritableThreadLocal肛度。。投慈。
子線程2:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@75f4c190

四承耿、總結(jié)

  1. InheritableThreadLocal可以實(shí)現(xiàn)子線程獲取父線程的本地變量。
  2. 子線程初始化時(shí)伪煤,若父線程(當(dāng)前線程)的本地變量inheritableThreadLocals不為null加袋,則復(fù)制給子線程。
  3. ThreadLocal留個(gè)childValue的用意抱既,就是讓InheritableThreadLocal實(shí)現(xiàn)职烧,并且可以讓客戶端自定義重寫childValue對(duì)從父線程復(fù)制到子線程的值做特殊處理。
  4. 若父線程使用InheritableThreadLocal設(shè)置了自定義引用類型的值防泵,復(fù)制給子線程時(shí)存在并發(fā)問題蚀之,需要自行實(shí)現(xiàn)childValue的深拷貝。

拋個(gè)問題:

如果使用線程池創(chuàng)建子線程捷泞,子線程只會(huì)初始化一次恬总,父線程中使用InheritableThreadLocal設(shè)置值,因?yàn)閺?fù)制機(jī)制是在線程初始化的時(shí)候肚邢,那么父線程只有在線程池初始化子線程時(shí)同步復(fù)制一次數(shù)據(jù),后續(xù)父線程再修改值拭卿,就無法同步更新到線程池中的子線程了骡湖,這該怎么辦呢?

只要在每次提交任務(wù)時(shí)復(fù)制就可以了峻厚,這就要對(duì)線程池以及InheritableThreadLocal做一些定制化處理响蕴,讓復(fù)制機(jī)制放在每次提交任務(wù)的時(shí)候,阿里有一個(gè)開源項(xiàng)目給出了解決方案https://github.com/alibaba/transmittable-thread-local惠桃,后續(xù)可深入了解其實(shí)現(xiàn)原理浦夷。

PS: 如若文章中有錯(cuò)誤理解辖试,歡迎批評(píng)指正,同時(shí)非常期待你的評(píng)論劈狐、點(diǎn)贊和收藏罐孝。我是徐同學(xué),愿與你共同進(jìn)步肥缔!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莲兢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子续膳,更是在濱河造成了極大的恐慌改艇,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坟岔,死亡現(xiàn)場(chǎng)離奇詭異谒兄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)社付,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門承疲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘦穆,你說我怎么就攤上這事纪隙。” “怎么了扛或?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵绵咱,是天一觀的道長。 經(jīng)常有香客問我熙兔,道長悲伶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任住涉,我火速辦了婚禮麸锉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舆声。我一直安慰自己花沉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布媳握。 她就那樣靜靜地躺著碱屁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛾找。 梳的紋絲不亂的頭發(fā)上娩脾,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音打毛,去河邊找鬼柿赊。 笑死俩功,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碰声。 我是一名探鬼主播诡蜓,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奥邮!你這毒婦竟也來了万牺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤洽腺,失蹤者是張志新(化名)和其女友劉穎脚粟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蘸朋,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡核无,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藕坯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片团南。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炼彪,靈堂內(nèi)的尸體忽然破棺而出吐根,到底是詐尸還是另有隱情,我是刑警寧澤辐马,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布拷橘,位于F島的核電站,受9級(jí)特大地震影響喜爷,放射性物質(zhì)發(fā)生泄漏冗疮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一檩帐、第九天 我趴在偏房一處隱蔽的房頂上張望术幔。 院中可真熱鬧,春花似錦湃密、人聲如沸诅挑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揍障。三九已至,卻和暖如春俩由,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背癌蚁。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工幻梯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兜畸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓碘梢,卻偏偏與公主長得像咬摇,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子煞躬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354