在系統開發過程當中常使用ThreadLocal進行傳遞日誌的RequestId,由此來獲取整條請求鏈路。然而當線程中開啓了其餘的線程,此時ThreadLocal裏面的數據將會出現沒法獲取/讀取錯亂,甚至還可能會存在內存泄漏等問題,下面用代碼來演示一下這個問題。html
普通代碼示例:java
並行流代碼示例:git
ThreadLocal的子類InheritableThreadLocal其實已經幫咱們處理好了,經過這個組件能夠實現父子線程之間的數據傳遞,在子線程中可以父線程中的ThreadLocal本地變量。github
能夠看出InheritableThreadLocal繼承自ThreadLocal,並重寫了三個相關方法。api
再回來過來看ThreadLocal的源碼:緩存
咱們發現InheritableThreadLocal中createMap,以及getMap方法處理的對象不同了,其中在ThreadLocal中處理的是threadLocals,而InheritableThreadLocal中的是inheritableThreadLocals,咱們再順藤摸瓜看一下Thread對象的處理,其中在init源碼中咱們看到這麼一段代碼:oracle
代碼的意思是在Thread獲取先父親線程parent(即要建立子線程的當前這個線程)。當父親線程中對inherThreadLocals進行了賦值,就會把當前線程的本地變量(也就是父線程的inherThreadLocals)進行createInheritedMap方法操做。查看源碼createInheritedMap方法,源碼可知此操做就是將賦線程的threadLocalMap傳遞給子線程。框架
咱們寫個代碼測試一下:分佈式
看起來彷佛真的是解決了咱們沒法傳遞的問題。測試
測試結果顯示兩次賦值,獲得的結果仍是第一次的值!爲何?
其實緣由也很簡單,咱們的線程池會緩存使用過的線程。當線程須要被重複利用的時候,並不會再從新執行init()初始化方法,而是直接使用已經建立過的線程,因此這裏的值不會二次產生變化,那麼該怎麼作到真正的父子線程數據傳遞呢?
JDK的InheritableThreadLocal類能夠完成父線程到子線程的值傳遞。但對於使用線程池等會池化複用線程的組件的狀況,線程由線程池建立好,而且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal值傳遞已經沒有意義,應用須要的其實是把任務提交給線程池時的ThreadLocal值傳遞到任務執行時。
首先分析一下最核心的類:TransmittableThreadLocal
首先TransmittableThreadLocal繼承自InheritableThreadLocal,這樣能夠在不破壞原有InheritableThreadLocal特性的狀況下,還能充分使用Thread線程建立過程當中執行init方法,從而達到父子線程傳遞數據的目的。
這裏有一個很重要的變量holder:源碼以下
1. holder中存放的是InheritableThreadLocal本地變量。
2. WeakHashMap支持存放空置。
主要的幾個相關方法:源碼以下
1. get方法調用時,先獲取父親的相關數據判斷是否有數據,而後在holder中把自身也給加進去。
2. set方法調用時,先在父親中設置,再本地判斷是holder否爲刪除或者是新增數據。
3. remove調用時,先刪除自身,再刪除父親中的數據,刪除也是直接以自身this做爲變量Key。
採用包裝的形式來處理線程池中的線程不會執行初始化的問題,源碼以下:
1. 先取得holder。
2. 備份線程本地數據
3. run原先的方法
4. 還原線程本地數據
備份方法:
1. 先獲取holder中的數據
2. 進行迭代,數據在captured中不存在,可是holder中存在,說明是後來加進去的,進行刪除。
3. 再將captured設置到當前線程中。
還原方法:
1. 先獲取holder中的數據
2. backup中不存在,holder中存在,說明是後面加進去的,進行刪除還原操做。
3. 再將backup設置到當前線程中。
分佈式跟蹤系統
日誌收集記錄系統上下文
應用容器或上層框架跨應用代碼給下層SDK傳遞信息