ThreadLocal父子線程之間的數據傳遞問題


一、問題的提出

在系統開發過程中常使用ThreadLocal進行傳遞日志的RequestId,由此來獲取整條請求鏈路。然而當線程中開啟了其他的線程,此時ThreadLocal里面的數據將會出現無法獲取/讀取錯亂,甚至還可能會存在內存泄漏等問題,下面用代碼來演示一下這個問題。

普通代碼示例:

並行流代碼示例:

二、問題的解決

ThreadLocal的子類InheritableThreadLocal其實已經幫我們處理好了,通過這個組件可以實現父子線程之間的數據傳遞,在子線程中能夠父線程中的ThreadLocal本地變量。

三、源碼的分析

可以看出InheritableThreadLocal繼承自ThreadLocal,並重寫了三個相關方法。

再回來過來看ThreadLocal的源碼:

我們發現InheritableThreadLocal中createMap,以及getMap方法處理的對象不一樣了,其中在ThreadLocal中處理的是threadLocals,而InheritableThreadLocal中的是inheritableThreadLocals,我們再順藤摸瓜看一下Thread對象的處理,其中在init源碼中我們看到這么一段代碼:

代碼的意思是在Thread獲取先父親線程parent(即要創建子線程的當前這個線程)。當父親線程中對inherThreadLocals進行了賦值,就會把當前線程的本地變量(也就是父線程的inherThreadLocals)進行createInheritedMap方法操作。查看源碼createInheritedMap方法,源碼可知此操作就是將賦線程的threadLocalMap傳遞給子線程。

我們寫個代碼測試一下:

看起來似乎真的是解決了我們無法傳遞的問題。

四、真的就這么美好么?我們來和線程池搭配一下

測試結果顯示兩次賦值,得到的結果還是第一次的值!為什么?

其實原因也很簡單,我們的線程池會緩存使用過的線程。當線程需要被重復利用的時候,並不會再重新執行init()初始化方法,而是直接使用已經創建過的線程,所以這里的值不會二次產生變化,那么該怎么做到真正的父子線程數據傳遞呢?

五、真正的解決方案:阿里的transmittable-thread-local了解一下

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設置到當前線程中。

下面是幾個典型場景例子。

1. 分布式跟蹤系統

2. 日志收集記錄系統上下文

3. 應用容器或上層框架跨應用代碼給下層SDK傳遞信息

項目地址:https://github.com/alibaba/transmittable-thread-local


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM