最近接手了一個老專案,看到一個很有意思的現象。
這個專案中大量的方法入參都會帶上user資訊,比如這樣
它的意圖是希望在方法內使用user的資訊,但是如此大範圍的傳遞使用者資訊,第一感覺就是不優雅。那有什麼辦法可以優化一下呢?
我們第一反應是,可以存一個全域性變數,在初始位置將使用者資訊存入全域性變數,然後在需要的地方去get一下。
那在WEB應用中,每個請求都是一個獨立執行緒,怎麼去標記呢?
可以用執行緒的id去作為map的key,將該請求的使用者資訊作為map的value。
咦?很熟悉的感覺。
沒錯,Java已經幫我們封裝好了這麼一個物件,它就是我們今天要說的ThreadLocal。
- 什麼是ThreadLocal
- 如何使用ThreadLocal優化userid層層傳遞的問題
- ThreadLocal原理是啥
- ThreadLocal的其他使用場景
1.什麼是ThreadLocal
先來看下JDK的註釋:
簡單翻譯過來,就是說:
ThreadLocal提供了執行緒隔離的區域性變數,通過get( )和set( )方法操作當前執行緒對應的變數,而且不會和其他執行緒衝突,實現了基於執行緒的資料隔離。
2.如何使用ThreadLocal進行優化
話不多說,基於我們開頭的例子,我迫不及待地用ThreadLocal來優化一下。
2.1 構建基於ThreadLocal的上下文
定義一個SessionUser類,儲存使用者資訊,包括使用者id、使用者名稱。
然後定義一個基於ThreadLocal的上下文SessionUserContext,程式碼如下所示。
2.2 資訊存入ThreadLocal中
在我們的優化案例中,就是存入使用者資訊。
解析請求中的使用者資訊有很多方法。本文以HandlerIntercept為例,說明下MVC中的一種方式。
- 實現HandlerIntercept介面
- 重寫preHandler方法
- 解析HttpServletRequest,獲取使用者資訊
- 使用者資訊存於SessionUserContext
原始碼如下所示。
2.3 在需要的地方獲取資訊
原本需要傳入CurrentUser的引數都可以去掉了。
在需要使用者資訊的時候,直接從SessionUserContext中獲取即可。
哈哈,是不是看起來一下子清爽了很多。
可以在任何地方獲取user資訊,不再需要層層傳遞使用者資訊了。
3.ThreadLocal實現原理
上面我們已經知道了怎麼通過ThreadLocal進行優化。
下面,我們要 知其然知其所以然,一起看看ThreadLocal實現原理吧。
3.1 set方法
Set方法應該是ThreadLocal的核心邏輯了。
主要三步:
- 獲取當前執行緒
- 獲取ThreadLocalMap物件
- 如果ThreadLocalMap物件存在,則將當前執行緒物件作為key,要儲存的物件作為value存到map中 如果ThreadLocalMap物件不存在,就呼叫creatMap( )進行建立
3.2 ThreadLocalMap是什麼。
ThreadLocalMap是一個定義在ThreadLocal類內部的靜態類,裡面還定義了一個Entry類作為儲存值的地方。
ThreadLocalMap的key是當前ThreadLocal物件,value是我們要儲存的值(物件)。
呼叫creatMap的時候,就是新建一個ThreadLocalMap物件
同時,ThreadLocalMap在Thread類中作為一個屬性存在。
每個執行緒Thread維護了ThreadLocalMap這麼一個Map,這個map的key是LocalThread物件本身,value則是要儲存的物件
3.3 get方法
Get方法就比較簡單了,就是從map中取值的過程。
3.4 ThreadLocal小結
現在,讓我們重新梳理一遍,看看ThreadLocal是如何實現變數的執行緒隔離的:
- 每個Thread維護著一個ThreadLocalMap的引用
- ThreadLocalMap是ThreadLocal的內部類,用Entry來進行儲存,key是ThreadLocal物件,值是傳遞進來的物件
- 呼叫ThreadLocal的get()/set()方法時,實際上就是以ThreadLocal物件為key,在ThreadLocalMap中讀寫value
4.實戰要點
在一開始的優化設計中,不知道大家有沒有注意到對ThreadLocal的remove呼叫。
這裡就需要談談ThreadLocal使用時的,兩個要點。尤其是在使用執行緒池的時候使用ThreadLocal。
4.1 避免記憶體洩露
在ThreadLocalMap介紹的時候,我們可以看到,ThreadLocalMap是Thread的一個屬性。因此,ThreadLocalMap和Thread的生命週期是一樣的。
如果沒有手動刪除對應的ThreadLocal的key,那麼就會造成記憶體洩漏無法回收。尤其線上程池環境下,執行緒會被不斷複用。
4.2 執行緒池避免重複執行緒變數影響
以前文優化案例為例。
在MVC中,每次請求進來會使用執行緒池複用執行緒。如果請求帶了使用者資訊,那麼就會重置ThreadLocal對應的使用者資訊,如果請求沒有帶使用者資訊,必須手動清除一下當前ThreadLocal對應的變數,否則後面使用過程中可能會造成混亂。
都看到最後了,原創不易,點個關注,點個贊吧~
文章持續更新,可以微信搜尋「阿丸筆記 」第一時間閱讀,回覆關鍵字【學習】有我準備的一線大廠面試資料。
知識碎片重新梳理,構建Java知識圖譜:github.com/saigu/JavaK…(歷史文章查閱非常方便)