tft每日頭條

 > 生活

 > threadlocal源碼圖解

threadlocal源碼圖解

生活 更新时间:2025-01-10 16:32:30
一丶ThreadLocal結構

threadlocal源碼圖解(ThreadLocal源碼學習筆記)1

每一個Thread對象都有一個名為 threadLocals 類型為 ThreadLocal.ThreadLocalMap 的屬性, ThreadLocal.ThreadLocalMap 對象内部存在一個 Entry 數組,其中存儲的Entry對象key是 ThreadLocal ,value便是我們綁定在線程上的值。ThreadLocal可以做到線程隔離是由于每一個線程對象持有一個ThreadLocalMap,每一個線程對ThreadLocalMap的處理是互不影響的。之所以持有的是ThreadLocalMap,是線程可能使用多個ThreadLocal存儲數據,比如在Spring事務同步管理器中 TransactionSynchronizationManager 包含三個ThreadLocal對象,一個管理事務相關資源,一個管理當前事務需要回調的同步接口,一個管理事務名稱,三個ThreadLocal對象對應着當前 Thread 持有的 ThreadLocal.ThreadLocalMap 中Entry數組的的三個Entry

二丶源碼學習1.set(T value)——向ThreadLocal中設置值

threadlocal源碼圖解(ThreadLocal源碼學習筆記)2

拿到當前線程 Thread.currentThread() 這是一個Native方法, getMap 方法便是獲取線程中的 ThreadLocal.ThreadLocalMap threadLocals 屬性,包裝成方法便于子類重寫覆蓋。如果當前線程的 ThreadLocalMap 不為空那麼向 ThreadLocalMap 中設置值,反之調用 createMap 初始化map。通常第一次設置值的時候 ThreadLocalMap 為空。

2.createMap(Thread t, T firstValue)——為線程初始化ThreadLocalMap

threadlocal源碼圖解(ThreadLocal源碼學習筆記)3

方法很簡單直接調用ThreadLocalMap的構造函數,在研究此構造函數之前我們先看下ThreadLocalMap的結構,其包含一個 Entry 數組,其中Entry繼承了 WeakReference

threadlocal源碼圖解(ThreadLocal源碼學習筆記)4

2.1為什麼這裡Entry保存ThreadLocal類型的key使用弱引用:

我們知道弱引用具備的性質:在垃圾回收器線程掃描它所管轄的内存區域的過程中,一旦發現了 隻具有弱引用 指向的對象,不管當前内存空間足夠與否,都會回收它的内存。這裡使用弱應用是為了防止oom,如果ThreadLocal作為Key不使用弱引用,如果根據可達性算法此ThreadLocal已經無法和GCRoot關聯(沒有任何強引用指向當前ThreadLocal),但是當前線程并沒有結束,可以通過當前線程關聯到其 threadLocals 屬性對應的 ThreadLocalMap ,再關聯到Entry中的ThreadLocal對象,這時候ThreadLocal将永遠無法被回收。

threadlocal源碼圖解(ThreadLocal源碼學習筆記)5

這裡我們給出一個啟動線程執行死循環,再死循環中創建ThreadLocal并set,這段代碼執行并不會發生OOM,原因是ThreadLocal是被弱引用指向,在發生GC的時候會被回收。

threadlocal源碼圖解(ThreadLocal源碼學習筆記)6

這裡應該還有一個問題,雖然ThreadLocal被回收了,但是Entry數組一直在塞入Entry,回收之後就相當于Entry的key為null,value存在值,那麼為什麼不會oom昵,原因是往ThreadLocalMap中塞入元素的時候,會删除掉過時(指Entry中的key弱引用持有的ThreadLocal為null)的元素。

2.2 ThreadLocalMap構造方法

threadlocal源碼圖解(ThreadLocal源碼學習筆記)7

這裡使用到 ThreadLocal.threadLocalHashCode 此值由 nextHashCode 方法生成,其使用 AtomicInteger 原子類生成

threadlocal源碼圖解(ThreadLocal源碼學習筆記)8

其中 firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1 是為了讓hash分布均勻減少hash沖突(類似于HashMap中高位低位進行異或),至于為什麼使用 0x61c88647 我沒有深究。

setThreshold 方法是使用屬性 threshold 記錄當前Entry數組長度的 2/3 作為擴容阈值,擴容邏輯後續進行解析。

3.ThreadLocalMap#set(ThreadLocal<?> key, Object value) 存入數據

set方法的邏輯可以分作兩部分:1.使用開放地址法找到合适的位置存儲數據,2.向數組中放入新Entry,有需要的話擴容

3.1.使用開放地址法找到合适的位置存儲數據

threadlocal源碼圖解(ThreadLocal源碼學習筆記)9

第一個if 意味着是相同的ThreadLocal,類似于HashMap put相同key的元素多次,後續的後覆蓋前面的,這裡也一樣,進行覆蓋。

第二個if 意味着,原來霸占Entry數組位置的ThreadLocal弱應用持有的ThreadLocal被回收了會調用 replaceStaleEntry 覆蓋

3.2向數組中放入新Entry,有需要的話擴容

上面for循環進行的條件是 e != null ,e是Entry數組中元素,那麼結束for循環,除了成功覆蓋原有元素的還有找到一個可以使用的位置

threadlocal源碼圖解(ThreadLocal源碼學習筆記)10

這裡擴容的條件有兩個 cleanSomeSlots 删除過期的條目失敗,且 當前Entry數組存入元素大于擴容阈值

threadlocal源碼圖解(ThreadLocal源碼學習筆記)11

擴容代碼如下,遍曆所有的元素,如果已經被回收了那麼将value也置為null,如果沒有被回收那麼将元素拷貝到新的位置

threadlocal源碼圖解(ThreadLocal源碼學習筆記)12

這裡為什麼要将value也置為空昵

首先ThreadLocal的key 已經被回收了,這時候調用者沒辦法拿到被回收key對應的value,所有置為null是不會影響到使用的。

關鍵的是 Help the GC 的注釋,置為null可以幫助jvm進行GC,我們首先看下如下方法

threadlocal源碼圖解(ThreadLocal源碼學習筆記)13

此方法也不會發生OOM

threadlocal源碼圖解(ThreadLocal源碼學習筆記)14

理論直接将被回收Entry位置的元素置為null,這時候也是無法通過GC Root應用到Entry,自然也無法引用到String對象,直接置為null也是相應的目的

這裡擴容複制元素沒有像HashMap進行地位不變,高位翻倍的操作,還是使用開放地址法找到合适的位置。

4.ThreadLocal#get()——獲取和當前線程綁定在此ThreadLocal上的值

threadlocal源碼圖解(ThreadLocal源碼學習筆記)15

這部分代碼分為兩部分看:

4.1獲取ThreadLocalMap中的值

獲取當前線程中的ThreadLocalMap屬性,以當前ThreadLocal作為key獲取到對應的值,具體獲取的邏輯在 ThreadLocalMap#getEntry 方法

threadlocal源碼圖解(ThreadLocal源碼學習筆記)16

首先是對Entry數組的長度進行取模,獲取當前ThreadLocal對應的位置,如果存在,且Entry中的ThreadLocal和當前入參的ThreadLocal相同(之所以需要這麼判斷是因為,hash沖突後當前ThreadLocal會被放在後續的位置,隻有二者的地址相同才能返回),那麼返回。之所以判斷 e!=null 可能是當前線程先删除再get,這時候不判斷會抛出空指針。

threadlocal源碼圖解(ThreadLocal源碼學習筆記)17

getEntryAfterMiss 方法并不複雜,就是利用 nextIndex 找下一個位置,類似于HashMap中拉鍊法需要遍曆鍊表一樣,如果下一個位置為null,說明當前ThreadLocal沒有存儲過,直接返回null

4.2ThreadLocalMap沒有初始化,或者沒有從ThreadLocalMap中獲取到對應的值

這裡會直接調用 setInitialValue 方法

threadlocal源碼圖解(ThreadLocal源碼學習筆記)18

其中 initialValue() 方法是給子類複寫提供的方法,我們可以如下為ThreadLocal設置初始值

threadlocal源碼圖解(ThreadLocal源碼學習筆記)19

也可以使用ThreadLocal提供的靜态工廠方法,如

threadlocal源碼圖解(ThreadLocal源碼學習筆記)20

使用此靜态方法返回的是 SuppliedThreadLocal 其 initialValue 方法會調用傳入的Supplie,兩種方法都可以自定義ThreadLocal沒有設置值的時候返回的初始值

5.ThreadLocal#remove()

threadlocal源碼圖解(ThreadLocal源碼學習筆記)21

首先自然是獲取當前線程的ThreadLocalMap,如果初始化了才進行删除,然後調用 ThreadLocalMap#remove 方法,把當前ThreadLocal作為key

threadlocal源碼圖解(ThreadLocal源碼學習筆記)22

删除過期條目的 expungeStaleEntry 方法,會将Entry數組中過期的條目(弱引用被回收,或者被删除的條目)置為null。

三丶InheritableThreadLocal支持繼承的ThreadLocal

這裡說的繼承是指父線程往 InheritableThreadLocal 設置了值,然後父線程開啟子線程,子線程的 InheritableThreadLocal 會拷貝其中的值

threadlocal源碼圖解(ThreadLocal源碼學習筆記)23

如上圖,運行 test5() 方法的線程是main線程,首先向其中設置值 parent ,然後開啟子線程,子線程運行直接使用get并打印出 parent 。具體原理是 Thread 的構造方法會拿到當前線程中的 inheritableThreadLocals 内容複制到子線程的 inheritableThreadLocals 中

threadlocal源碼圖解(ThreadLocal源碼學習筆記)24

這裡調用了 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals) 将返回值設置到創建線程的 inheritableThreadLocals 屬性上

threadlocal源碼圖解(ThreadLocal源碼學習筆記)25

邏輯也很簡單,遍曆父線程中的entry元素,調用 childValue 方法,實現父Entry值映射成子Entry值( InheritableThreadLocal 默認直接信息映射,如有需要可以覆蓋 childValue 方法),然後使用開放地址法存到子線程中。

其中 InheritableThreadLocal 還重寫了 getMap , createMap 方法,二者都操作Thread中的 inheritableThreadLocals 屬性

threadlocal源碼圖解(ThreadLocal源碼學習筆記)26

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved