ThreadlLocal詳解

1嘉抓、ThreadlLocal

  • 簡介:ThreadLocal提供線程的局部變量填抬,每個線程都可以通過get()和set()對局部變量進行操作而不會對其他線程的局部變量產生影響佛玄,實現(xiàn)了線程之間的數(shù)據(jù)隔離婚夫。e.g. 數(shù)據(jù)庫的連接池管理新翎。

  • 原理:

    • 每個Thread維護著一個ThreadLocalMap的引用
    • ThreadLocalMap是ThreadLocal的內部類疗疟,用Entry來進行存儲
    • 調用ThreadLocal的set()方法時该默,實際上就是往ThreadLocalMap設置值,key是ThreadLocal對象策彤,值是傳遞進來的對象
    • 調用ThreadLocal的get()方法時栓袖,實際上就是往ThreadLocalMap獲取值匣摘,key是ThreadLocal對象
    • ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value裹刮。
    public void set(T value) {
            // 得到當前線程對象
            Thread t = Thread.currentThread();
            // 這里獲取ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            // 如果map存在音榜,則將當前線程對象t作為key,要存儲的對象作為value存到map里面去
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
    }
    
    static class ThreadLocalMap {
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            //....很長
    }
    
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
    }
    
    ThreadLocal.ThreadLocalMap threadLocals = null
    
  • 不足:無法完成父子線程之間的數(shù)據(jù)傳遞捧弃。

2赠叼、InheritableThreadLocal

  • 簡介:ThreadLocal的升級版,jdk原生违霞,實現(xiàn)了父子線程之間的數(shù)據(jù)傳遞嘴办。

  • 原理:重新開辟一塊空間(InheritableThreadLocal)用于存儲父子線程共用的數(shù)據(jù)隔離空間。重寫了ThreadLocal的三個方法买鸽,當創(chuàng)建新線程的時候涧郊,將父線程的ThreadLocal值拷貝到子線程。

    /** 
    * 該函數(shù)在父線程創(chuàng)建子線程眼五,向子線程復制InheritableThreadLocal變量時使用 
    */
    protected T childValue(T parentValue) { 
      return parentValue; 
    }
    /** 
    * 由于重寫了getMap底燎,操作InheritableThreadLocal時, 
    * 將只影響Thread類中的inheritableThreadLocals變量弹砚, 
    * 與threadLocals變量不再有關系 
    */ 
    ThreadLocalMap getMap(Thread t) {
      return t.inheritableThreadLocals; 
    }
    /** 
    * 類似于getMap双仍,操作InheritableThreadLocal時, 
    * 將只影響Thread類中的inheritableThreadLocals變量桌吃, 
    * 與threadLocals變量不再有關系 
    */ 
    void createMap(Thread t, T firstValue) {
      t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); 
    }
    
    public class Thread implements Runnable {
       ......(其他源碼)
        /* 
         * 當前線程的ThreadLocalMap朱沃,主要存儲該線程自身的ThreadLocal
         */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal,自父線程集成而來的ThreadLocalMap茅诱,
         * 主要用于父子線程間ThreadLocal變量的傳遞
         * 本文主要討論的就是這個ThreadLocalMap
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ......(其他源碼)
    }
    /**
        * 初始化一個線程.
        * 此函數(shù)有兩處調用逗物,
        * 1、上面的 init()瑟俭,不傳AccessControlContext翎卓,inheritThreadLocals=true
        * 2、傳遞AccessControlContext摆寄,inheritThreadLocals=false
        */
    private void init(ThreadGroup g, Runnable target, String name,
                         long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            ......(其他代碼)
    
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
            ......(其他代碼)
    }
    
  • 不足:無法完成線程池間的數(shù)據(jù)傳遞失暴。

3、Transmittable-Thread-Local

InheritableThreadLocal value 串位問題的根本原因在于它依賴 Thread 類本身的機制傳遞 value, 而 Thread 類由于其于線程池內 “復用存在” 的形式而導致 InheritableThreadLocal 的機制失效; 所以針對 InheritableThreadLocal 的改進, 突破點就在于如何擺脫對 Thread 類的依賴微饥。

  • 簡介:阿里開源組件逗扒,繼承自InheritableThreadLocal,實現(xiàn)了父子線程在線程池中的數(shù)據(jù)傳遞欠橘。

  • 原理:在提交任務給線程池時矩肩,將ThreadLocal數(shù)據(jù)一起提交,相當于重新set一次ThreadLocal肃续,「把任務提交給線程池時的ThreadLocal值傳遞到 任務執(zhí)行時」黍檩。

    • 與InheritableThreadLocal類似叉袍,單獨搞了一塊空間,維護一個全局的靜態(tài)變量holder刽酱,存儲所有TransmittableThreadLocal實例

      static ThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new ThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
          @Override
          protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
              return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
          }
      };
      
    • 提供一個 copy() 方法實時復制所有 TransmittableThreadLocal 實例及其在當前線程的 value

      static Map<TransmittableThreadLocal<?>, Object> copy() {
          Map<TransmittableThreadLocal<?>, Object> copy = new HashMap<TransmittableThreadLocal<?>, Object>();
          for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
              copy.put(threadLocal, threadLocal.copyValue());
          }
          return copy;
      }
      
    • 封裝了原始的 Runnable 和 Callable畦韭,將當前線程下所有的 TransmittableThreadLocal 實例及其對應的 value, 放到了一個 AtomicReference 包裝的 map 之中, 這樣就完成了由父線程向 Runnable的value 傳遞

      private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
          this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
          this.runnable = runnable;
          this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
      }
      
    • 關鍵的 run() 方法的處理

      public void run() {
          Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
          // 非核心邏輯已省略
          ......
          Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
          try {
              runnable.run();
          } finally {
              TransmittableThreadLocal.restoreBackup(backup);
          }   
      }
      
  • 不足:對hystrix修飾的線程池無效,因為hytrix做了線程隔離肛跌。

參考資料:

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末艺配,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衍慎,更是在濱河造成了極大的恐慌转唉,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稳捆,死亡現(xiàn)場離奇詭異赠法,居然都是意外死亡,警方通過查閱死者的電腦和手機乔夯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門砖织,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人末荐,你說我怎么就攤上這事侧纯。” “怎么了甲脏?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵眶熬,是天一觀的道長。 經常有香客問我块请,道長娜氏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任墩新,我火速辦了婚禮贸弥,結果婚禮上,老公的妹妹穿的比我還像新娘海渊。我一直安慰自己绵疲,他們只是感情好,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布切省。 她就那樣靜靜地躺著最岗,像睡著了一般帕胆。 火紅的嫁衣襯著肌膚如雪朝捆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天懒豹,我揣著相機與錄音芙盘,去河邊找鬼驯用。 笑死,一個胖子當著我的面吹牛儒老,可吹牛的內容都是我干的蝴乔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼驮樊,長吁一口氣:“原來是場噩夢啊……” “哼薇正!你這毒婦竟也來了?” 一聲冷哼從身側響起囚衔,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤挖腰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后练湿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猴仑,經...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年肥哎,在試婚紗的時候發(fā)現(xiàn)自己被綠了辽俗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡篡诽,死狀恐怖崖飘,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情杈女,我是刑警寧澤坐漏,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站碧信,受9級特大地震影響赊琳,放射性物質發(fā)生泄漏。R本人自食惡果不足惜砰碴,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一躏筏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呈枉,春花似錦趁尼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啃憎,卻和暖如春芝囤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工悯姊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羡藐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓悯许,卻偏偏與公主長得像仆嗦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子先壕,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354