來源| OSCHINA 社區
作者 | 京東云開發者-京東物流 閆鵬勃
1 什么是 ThreadLocal?
ThreadLocal 是一個關于創建線程局部變量的類。
通常情況下,我們創建的變量是可以被任何一個線程訪問并修改的。而使用 ThreadLocal 創建的變量只能被當前線程訪問,其他線程則無法訪問和修改。ThreadLocal 在設計之初就是為解決并發問題而提供一種方案,每個線程維護一份自己的數據,達到線程隔離的效果。
2 有什么作用?
2.1 set once,get everywhere
在現在的系統設計中,前后端分離已基本成為常態,分離之后如何獲取用戶信息就成了一件麻煩事,通常在用戶登錄后, 用戶信息會保存在 Session 或者 Token 中。這個時候,我們如果使用常規的手段去獲取用戶信息會很費勁,拿 Session 來說,我們要在接口參數中加上 HttpServletRequest 對象,然后調用 getSession 方法,且每一個需要用戶信息的接口都要加上這個參數,才能獲取 Session,這樣實現就很麻煩了。 在實際的系統設計中,我們肯定不會采用上面所說的這種方式,而是使用 ThreadLocal,我們會選擇在攔截器的業務中, 獲取到保存的用戶信息,然后存入 ThreadLocal,那么當前線程在任何地方如果需要拿到用戶信息都可以使用 ThreadLocal 的 get () 方法 (異步程序中 ThreadLocal 是不可靠的)
2.2 線程安全,空間換時間
在 Spring 的 Web 項目中,我們通常會將業務分為 Controller 層,Service 層,Dao 層, 我們都知道@Autowired 注解默認使用單例模式,那么不同請求線程進來之后,由于 Dao 層使用單例,那么負責數據庫連接的 Connection 也只有一個, 如果每個請求線程都去連接數據庫,那么就會造成線程不安全的問題,Spring 是如何解決這個問題的呢? 在 Spring 項目中 Dao 層中裝配的 Connection 肯定是線程安全的,其解決方案就是采用 ThreadLocal 方法,當每個請求線程使用 Connection 的時候, 都會從 ThreadLocal 獲取一次,如果為 null,說明沒有進行過數據庫連接,連接后存入 ThreadLocal 中,如此一來,每一個請求線程都保存有一份 自己的 Connection。于是便解決了線程安全問題
3 ThreadLocal 實戰應用
3.1 ehr 中的使用
在登錄攔截器中將用戶信息寫入,后續使用時方便取值
3.2 分頁插件 PageHelper 中的應用
3.3 AopContext
4 源碼解讀
你是否有這樣的疑惑?為什么可以直接拿到?對象存放在哪里?存在什么問題?
4.1 get 方法
在 get () 方法中也會獲取到當前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則把獲取 key 為當前 ThreadLocal 的值;否則調用 setInitialValue () 方法返回初始值,并保存到新創建的 ThreadLocalMap 中。
4.2 set 方法
調用 set 時,直接調用 set (T value) 方法中,首先獲取當前線程,然后在獲取到當前線程的 ThreadLocalMap,如果 ThreadLocalMap 不為 null,則將 value 保存到 ThreadLocalMap 中,并用當前 ThreadLocal 作為 key;否則創建一個 ThreadLocalMap 并給到當前線程,然后保存 value。 ThreadLocalMap 相當于一個 HashMap,是真正保存值的地方
map 的 set,如果 map 為空,則創建一個
4.3 initialValue () 方法
initialValue () 是 ThreadLocal 的初始值,默認返回 null,子類可以重寫改方法,用于設置 ThreadLocal 的初始值。
4.4 remove () 方法
ThreadLocal 還有一個 remove () 方法,用來移除當前 ThreadLocal 對應的值。同樣也是同過當前線程的 ThreadLocalMap 來移除相應的值。
getMap 拿到了什么?
在 set,get,initialValue 和 remove 方法中都會獲取到當前線程,然后通過當前線程獲取到 ThreadLocalMap,如果 ThreadLocalMap 為 null,則會創建一個 ThreadLocalMap,并給到當前線程
此處 t 是 Thread,直接可以 “點” 拿到這個 map
每個 Thread 對象內部都維護了一個 ThreadLocalMap 這樣一個 ThreadLocal 的 Map,可以存放若干個 ThreadLocal
在使用 ThreadLocal 類型變量進行相關操作時,都會通過當前線程獲取到 ThreadLocalMap 來完成操作。每個線程的 ThreadLocalMap 是屬于線程自己的,ThreadLocalMap 中維護的值也是屬于線程自己的。這就保證了 ThreadLocal 類型的變量在每個線程中是獨立的,在多線程環境下不會相互影響。
5 使用注意事項
1)有可能導致內存泄漏,使用完畢后,需要 remove 在 ThreadLocalMap 的 set (),get () 和 remove () 方法中,都有清除無效 Entry 的操作,這樣做是為了降低內存泄漏發生的可能。
Entry 中的 key 使用了弱引用的方式,這樣做是為了降低內存泄漏發生的概率,但不能完全避免內存泄漏。
假設 Entry 的 key 沒有使用弱引用的方式,而是使用了強引用:由于 ThreadLocalMap 的生命周期和當前線程一樣長,那么當引用 ThreadLocal 的對象被回收后,由于 ThreadLocalMap 還持有 ThreadLocal 和對應 value 的強引用,ThreadLocal 和對應的 value 是不會被回收的,這就導致了內存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 沒有被回收而導致的內存泄漏,但是此時 value 仍然是無法回收的,依然會導致內存泄漏。
ThreadLocalMap 已經考慮到這種情況,并且有一些防護措施:在調用 ThreadLocal 的 get (),set () 和 remove () 的時候都會清除當前線程 ThreadLocalMap 中所有 key 為 null 的 value。這樣可以降低內存泄漏發生的概率。所以我們在使用 ThreadLocal 的時候,每次用完 ThreadLocal 都調用 remove () 方法,清除數據,防止內存泄漏。
2)使用線程池時,父子線程傳遞慎用,因為初始化時機為線程創建時
3)針對 2 有什么方案可以解決?
TransmittableThreadLocal
源碼地址:https://github.com/alibaba/transmittable-thread-local
審核編輯:湯梓紅
-
數據庫
+關注
關注
7文章
3799瀏覽量
64390 -
源碼
+關注
關注
8文章
641瀏覽量
29213 -
spring
+關注
關注
0文章
340瀏覽量
14343 -
變量
+關注
關注
0文章
613瀏覽量
28370 -
線程
+關注
關注
0文章
504瀏覽量
19683
原文標題:ThreadLocal 源碼解析及實戰應用
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論