InheritableThreadLocal NullPointException & 線程池環(huán)境下使用TTL進(jìn)行線程上下文傳遞

InheritableThreadLocal NullPointException & 線程池環(huán)境下使用TTL進(jìn)行線程上下文傳遞

背景:因?yàn)闃I(yè)務(wù)需要,在某個(gè)接口處理超過3秒逛裤,就即時(shí)返回。因此我使用了Future的 超時(shí)特性猴抹。然后我又用線程池去處理Future任務(wù)带族。同時(shí)我之前又加了一個(gè)切面,那么切面有一個(gè)InheritableThreadLocal變量蟀给,用于存放請求上下文信息蝙砌。原來是ThreadLocal,但是子線程也需要用到跋理,所以切換到了InheritableThreadLocal择克。就是這個(gè)InheritableThreadLocal在線程池中導(dǎo)致了NPE問題

關(guān)鍵詞:InheritableThreadLocal 空指針異常 Future NPE

原因

InheritableThreadLocal 無法在線程池中獲取到父線程的信息。我這里前普,F(xiàn)uture 是在線程池中執(zhí)行的肚邢,所以導(dǎo)致了InheritableThreadLocal 拋出 NullPointException

解決方案

先說解決方案

  1. 不使用線程池,每次執(zhí)行都new 一個(gè)線程拭卿。

  2. 使用阿里的 transmittable-thread-local

    1. 引入maven

      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>transmittable-thread-local</artifactId>
          <version>2.12.4</version>
      </dependency>
      
    2. 修飾RunnableCallable

      Runnable
      // 全局變量
      TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>(); // 使用TTL傳遞
      /*------------------在父線程中設(shè)置---------------------*/
      context.set("value-set-in-parent");
      /*------------------在父線程中設(shè)置---------------------*/
          
      
      /*------------------額外的處理---------------------*/
      Runnable task = new RunnableTask();
      // 額外的處理骡湖,生成修飾了的對象ttlRunnable
      Runnable ttlRunnable = TtlRunnable.get(task);
      executorService.submit(ttlRunnable);
      /*------------------額外的處理---------------------*/
      
      
      /*------------------在子線程中使用---------------------*/
      // Task中可以讀取,值是"value-set-in-parent"
      String value = context.get();
      /*------------------在子線程中使用---------------------*/
      
      
      Callable
      // 全局變量
      TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>(); // 使用TTL傳遞
      /*------------------在父線程中設(shè)置---------------------*/
      context.set("value-set-in-parent");
      /*------------------在父線程中設(shè)置---------------------*/
          
      
      /*------------------額外的處理---------------------*/
      Callable call = new CallableTask();
      // 額外的處理峻厚,生成修飾了的對象ttlRunnable
      Callable ttlCallable = TtlCallable.get(call);
      executorService.submit(ttlCallable);
      /*------------------額外的處理---------------------*/
      
      
      /*------------------在子線程中使用---------------------*/
      // Task中可以讀取勺鸦,值是"value-set-in-parent"
      String value = context.get();
      /*------------------在子線程中使用---------------------*/
      
      

分析

我喜歡刨根問底,沒有得到本質(zhì)目木,到時(shí)候還是會忘記的换途。這里只分析通信過程懊渡,忽略保證線程安全等細(xì)節(jié)東西。這樣不會影響主流程

線程之間如何通過InheritableThreadLocal & ThreadLocal 通信军拟?

看源碼可以看到剃执,ThreadLocal 本質(zhì)只是操作 Thread 類的 threadLocals 成員變量的一個(gè)工具而已。

Thread的對2個(gè)成員變量的說明:

/* 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;

InheritableThreadLocal 繼承了 ThreadLocal 懈息,和父類ThreadLocal 本質(zhì)沒有什么區(qū)別肾档,只是變成了操作 Thread類 的 inheritableThreadLocals 成員變量。

ThreadLocal對應(yīng)代碼:

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();
}

// 初始化map的方法 get 或者 set的時(shí)候如果補(bǔ)存在就會初始化
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 

InheritableThreadLocal重寫了父類ThreadLocalcreateMapgetMap 方法辫继,從而在初始化和賦值的時(shí)候怒见,變成了操作 inheritableThreadLocals

InheritableThreadLocal 對應(yīng)的代碼:

// 僅僅是重寫了ThreadLocal的getMap和createMap方法
ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}

void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

大致流程

因?yàn)闀r(shí)間有限,我只看了主流程姑宽,細(xì)枝末節(jié)還有細(xì)節(jié)得東西我都忽略了遣耍,并且是我個(gè)人得總結(jié),可能有不少紕漏炮车,僅供參考

我推測ThreadLocals和inheritableThreadLocals 是再Thread類出現(xiàn)之后才加的舵变。看注釋也驗(yàn)證了我這一點(diǎn)瘦穆。因?yàn)槲矣X得這樣設(shè)計(jì)有點(diǎn)破壞Thread類了纪隙。怎么說?InheritableThreadLocal 和 ThreadLocal 操作的 2個(gè)變量都是Thread類里面的扛或,InheritableThreadLocal 和 ThreadLocal 本質(zhì)只是操作那2個(gè)成員變量的工具而已绵咱。

  • ThreadLocal

    ThreadLocal 比較簡單,就是 set的時(shí)候熙兔,獲取到當(dāng)前線程的 threadLocals 變量麸拄,然后存取數(shù)據(jù)

  • InheritableThreadLocal

    InheritableThreadLocal 僅僅是在線程初始化的時(shí)候,繼承了一下父線程的 inheritableThreadLocals 變量黔姜,從而達(dá)到父子線程傳遞的目的拢切。

    大致流程如下:

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
    
博客-ThreadLocal 理解.png

inheritableThreadLocals 如何 做到在父線程和子線程直接傳遞?

在Thread 創(chuàng)建的時(shí)候秆吵,就把父線程的inheritableThreadLocals 賦值到當(dāng)前線程了淮椰。

關(guān)鍵代碼(Thread.java 的 418行):

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

為什么線程池中讀取不到?

線程池的線程已經(jīng)脫離了外層線程的體系纳寂,沒有什么關(guān)聯(lián)主穗。但是我不知道為什么,毙芜,并發(fā)量非常小得情況忽媒,,可以讀取到腋粥。...晦雨。架曹。。我之后有時(shí)間再更新吧

疑問

目前發(fā)現(xiàn)在并發(fā)量不高的情況下闹瞧,線程池會復(fù)用原有線程绑雄。但是并發(fā)一上去,線程池的線程就都是全新的奥邮,和原來的沒有關(guān)系

總結(jié)

我之后有時(shí)間再更新吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末万牺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洽腺,更是在濱河造成了極大的恐慌脚粟,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蘸朋,死亡現(xiàn)場離奇詭異核无,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)度液,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門厕宗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來画舌,“玉大人堕担,你說我怎么就攤上這事∏簦” “怎么了霹购?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朋腋。 經(jīng)常有香客問我齐疙,道長,這世上最難降的妖魔是什么旭咽? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任贞奋,我火速辦了婚禮,結(jié)果婚禮上穷绵,老公的妹妹穿的比我還像新娘轿塔。我一直安慰自己,他們只是感情好仲墨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布勾缭。 她就那樣靜靜地躺著,像睡著了一般目养。 火紅的嫁衣襯著肌膚如雪俩由。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天癌蚁,我揣著相機(jī)與錄音幻梯,去河邊找鬼兜畸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛礼旅,可吹牛的內(nèi)容都是我干的膳叨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼痘系,長吁一口氣:“原來是場噩夢啊……” “哼菲嘴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汰翠,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤龄坪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后复唤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體健田,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年佛纫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妓局。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呈宇,死狀恐怖好爬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甥啄,我是刑警寧澤存炮,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蜈漓,受9級特大地震影響穆桂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜融虽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一享完、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧有额,春花似錦般又、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至句狼,卻和暖如春笋熬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腻菇。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工胳螟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昔馋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓糖耸,卻偏偏與公主長得像秘遏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子嘉竟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容