用ThreadLocal來優化下程式碼吧

阿丸發表於2020-09-30

最近接手了一個老專案,看到一個很有意思的現象。

這個專案中大量的方法入參都會帶上user資訊,比如這樣

用ThreadLocal來優化下程式碼吧

 

它的意圖是希望在方法內使用user的資訊,但是如此大範圍的傳遞使用者資訊,第一感覺就是不優雅。那有什麼辦法可以優化一下呢?

用ThreadLocal來優化下程式碼吧

 

我們第一反應是,可以存一個全域性變數,在初始位置將使用者資訊存入全域性變數,然後在需要的地方去get一下。

那在WEB應用中,每個請求都是一個獨立執行緒,怎麼去標記呢?

可以用執行緒的id去作為map的key,將該請求的使用者資訊作為map的value。

咦?很熟悉的感覺。

用ThreadLocal來優化下程式碼吧

 

沒錯,Java已經幫我們封裝好了這麼一個物件,它就是我們今天要說的ThreadLocal。

  • 什麼是ThreadLocal
  • 如何使用ThreadLocal優化userid層層傳遞的問題
  • ThreadLocal原理是啥
  • ThreadLocal的其他使用場景

1.什麼是ThreadLocal

先來看下JDK的註釋:

用ThreadLocal來優化下程式碼吧

 

簡單翻譯過來,就是說:

ThreadLocal提供了執行緒隔離的區域性變數,通過get( )和set( )方法操作當前執行緒對應的變數,而且不會和其他執行緒衝突,實現了基於執行緒的資料隔離。

2.如何使用ThreadLocal進行優化

話不多說,基於我們開頭的例子,我迫不及待地用ThreadLocal來優化一下。

2.1 構建基於ThreadLocal的上下文

定義一個SessionUser類,儲存使用者資訊,包括使用者id、使用者名稱。

然後定義一個基於ThreadLocal的上下文SessionUserContext,程式碼如下所示。

用ThreadLocal來優化下程式碼吧

 


2.2 資訊存入ThreadLocal中

在我們的優化案例中,就是存入使用者資訊。

解析請求中的使用者資訊有很多方法。本文以HandlerIntercept為例,說明下MVC中的一種方式。

  • 實現HandlerIntercept介面
  • 重寫preHandler方法
  • 解析HttpServletRequest,獲取使用者資訊
  • 使用者資訊存於SessionUserContext

原始碼如下所示。

用ThreadLocal來優化下程式碼吧

 

2.3 在需要的地方獲取資訊

原本需要傳入CurrentUser的引數都可以去掉了。

在需要使用者資訊的時候,直接從SessionUserContext中獲取即可。

用ThreadLocal來優化下程式碼吧

 

哈哈,是不是看起來一下子清爽了很多。

可以在任何地方獲取user資訊,不再需要層層傳遞使用者資訊了。

用ThreadLocal來優化下程式碼吧

 

3.ThreadLocal實現原理

上面我們已經知道了怎麼通過ThreadLocal進行優化。

下面,我們要 知其然知其所以然,一起看看ThreadLocal實現原理吧。

3.1 set方法

Set方法應該是ThreadLocal的核心邏輯了。

主要三步:

  • 獲取當前執行緒
  • 獲取ThreadLocalMap物件
  • 如果ThreadLocalMap物件存在,則將當前執行緒物件作為key,要儲存的物件作為value存到map中 如果ThreadLocalMap物件不存在,就呼叫creatMap( )進行建立
用ThreadLocal來優化下程式碼吧

 

3.2 ThreadLocalMap是什麼。

ThreadLocalMap是一個定義在ThreadLocal類內部的靜態類,裡面還定義了一個Entry類作為儲存值的地方。

ThreadLocalMap的key是當前ThreadLocal物件,value是我們要儲存的值(物件)。

用ThreadLocal來優化下程式碼吧

 

呼叫creatMap的時候,就是新建一個ThreadLocalMap物件

用ThreadLocal來優化下程式碼吧

 

同時,ThreadLocalMap在Thread類中作為一個屬性存在。

用ThreadLocal來優化下程式碼吧

 

每個執行緒Thread維護了ThreadLocalMap這麼一個Map,這個map的key是LocalThread物件本身,value則是要儲存的物件

3.3 get方法

Get方法就比較簡單了,就是從map中取值的過程。

用ThreadLocal來優化下程式碼吧

 

3.4 ThreadLocal小結

現在,讓我們重新梳理一遍,看看ThreadLocal是如何實現變數的執行緒隔離的:

  • 每個Thread維護著一個ThreadLocalMap的引用
  • ThreadLocalMap是ThreadLocal的內部類,用Entry來進行儲存,key是ThreadLocal物件,值是傳遞進來的物件
  • 呼叫ThreadLocal的get()/set()方法時,實際上就是以ThreadLocal物件為key,在ThreadLocalMap中讀寫value

 

4.實戰要點

在一開始的優化設計中,不知道大家有沒有注意到對ThreadLocal的remove呼叫。

用ThreadLocal來優化下程式碼吧

 

這裡就需要談談ThreadLocal使用時的,兩個要點。尤其是在使用執行緒池的時候使用ThreadLocal。

4.1 避免記憶體洩露

用ThreadLocal來優化下程式碼吧

 

在ThreadLocalMap介紹的時候,我們可以看到,ThreadLocalMap是Thread的一個屬性。因此,ThreadLocalMap和Thread的生命週期是一樣的。

如果沒有手動刪除對應的ThreadLocal的key,那麼就會造成記憶體洩漏無法回收。尤其線上程池環境下,執行緒會被不斷複用。

4.2 執行緒池避免重複執行緒變數影響

以前文優化案例為例。

在MVC中,每次請求進來會使用執行緒池複用執行緒。如果請求帶了使用者資訊,那麼就會重置ThreadLocal對應的使用者資訊,如果請求沒有帶使用者資訊,必須手動清除一下當前ThreadLocal對應的變數,否則後面使用過程中可能會造成混亂。

 

都看到最後了,原創不易,點個關注,點個贊吧~
文章持續更新,可以微信搜尋「阿丸筆記 」第一時間閱讀,回覆關鍵字【學習】有我準備的一線大廠面試資料。
知識碎片重新梳理,構建Java知識圖譜:github.com/saigu/JavaK…(歷史文章查閱非常方便)

相關文章