作者:京東物流 閆鵬勃
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
詳解:https://www.jianshu.com/p/e0774f965aa3
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541542.html
標籤:其他
