天天看點

你問這誰會啊?ThreadLocal 父子線程之間該如何傳遞資料?

忘記之前是哪個公司面試的時候問到的,并不是一個常見的問題,我當時也沒回答正确,就按照線程通信那一套比如什麼 synchronized、Locks、volatile 啥的 XJB 說的,面試完找了些資料今天整理了下分享給大家~

ThreadLocal 的具體原理這篇文章就不解釋了,能幹啥大夥兒都倒背如流,其實就兩點:

  1. 鍊路透傳(通俗來說就是友善做參數傳遞,不用在調用方法時攜帶一堆請求參數)
  2. 線程隔離

每個線程都有自己的一個 ThreadLocalMap,ThreadLocal 持有的資料就是存在這個 Map 裡的(Thread.ThreadLocalMap threadLocals),是以能夠實作線程隔離,畢竟每個線程的 ThreadLocalMap 都是不一樣的

那如果子線程想要拿到父線程的中的 ThreadLocal 值怎麼辦呢?

比如會有以下的這種代碼的實作。在子線程中調用 get 時,我們拿到的 Thread 對象是目前子線程對象,對吧,每個線程都有自己獨立的 ThreadLocal,那麼目前子線程的 ThreadLocalMap 是 null 的(而父線程,也就是 main 線程中的 ThreadLocalMap 是有資料的),是以我們得到的 value 也是 null

public class ThreadLocalTest {
 private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args) throws  Exception{
        threadLocal.set("飛天小牛肉");
        System.out.println("父線程的值:"+ threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程的值:"+ threadLocal.get());
            }
        }).start();

        Thread.sleep(2000);
    }
}
           

結果輸出如下:

父線程的值:飛天小牛肉
子線程的值:null
           

要如何解決這個問題呢?

我們先來從 Thread 類中找找思路:

你問這誰會啊?ThreadLocal 父子線程之間該如何傳遞資料?

你會發現,在 ThreadLocalMap threadLocals 的下方,還有一個 ThreadLocalMap 變量 inherittableThreadLocals,inherit 翻譯為繼承

先看下這個變量的注釋:InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class.

oho,這裡出現了一個渣渣輝都從未體驗過的傳新類:InheritableThreadLocal

翻譯一下注釋,大概就是,如果你使用 InheritableThreadLocal,那麼儲存的資料都已經不在原來的 ThreadLocal.ThreadLocalMap threadLocals 裡面了,而是在一個新的 ThreadLocal.ThreadLocalMap inheritableThreadLocals 變量中了。

你問這誰會啊?ThreadLocal 父子線程之間該如何傳遞資料?

是以,如果想讓上面那段代碼中,子線程能夠拿到父線程的 ThreadLocal 值,隻需要把 ThreadLocal 聲明改為 InheritableThreadLocal 就可以了

下面我們具體來看下 InheritableThreadLocal 是怎麼做到父子線程傳值的。

首先看下 new Thread 的時候線程都做了些什麼 Thread#init()

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
 // 省略部分代碼
 Thread parent = currentThread();
     
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        // copy父線程的 map,建立一個新的 map 指派給目前線程的inheritableThreadLocals
  this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       
 // 省略部分代碼
   }
           

核心其實就是上面幾句代碼,如果你設定了 inheritableThreadLocals 變量,那麼 Thread 就會把父線程 ThreadLocal threadLocals 中的所有資料都 copy 到子線程的 InheritableThreadLocal inheritableThreadLocals 中。

而且,copy 調用的 createInheritedMap 方法其實是一個淺拷貝函數,key 和 value 都是原來的引用位址,這裡所謂的 copy 其實就是把一個 Map 中的資料複制到另一個 Map 中:

你問這誰會啊?ThreadLocal 父子線程之間該如何傳遞資料?

至此,大緻的解釋了 InheritableThreadLocal 為什麼能解決父子線程傳遞 Threadlcoal 值的問題了,總結下:

  1. 在建立 InheritableThreadLocal 對象的時候指派給線程的 t.inheritableThreadLocals 變量
  2. 在建立新線程的時候會 check 父線程中 t.inheritableThreadLocals 變量是否為 null,如果不為 null 則 copy 一份資料到子線程的 t.inheritableThreadLocals 成員變量中去
  3. InheritableThreadLocal 重寫了 getMap(Thread) 方法,是以 get 的時候,就會從 t.inheritableThreadLocals 中拿到 ThreadLocalMap 對象,進而實作了可以拿到父線程 ThreadLocal 中的值

來源:https://mp.weixin.qq.com/s/FEjbtx-LUsb0Yi4mlVm-uA

作者:小牛肉

繼續閱讀