優雅的使用 ThreadLocal
我們知道Java
的Web
專案大部分都是基於Tomcat
,每次訪問都是一個新的執行緒,看到這裡讓我們聯想到了ThreadLocal
,每一個執行緒都獨享一個ThreadLocal
,在接收請求的時候set
特定內容,在需要的時候get
這個值。下面我們就進入主題。
ThreadLocal
維持執行緒封閉性的一種更規範的方法就是使用ThreadLocal
,這個類能使執行緒中的某個值與儲存的值的物件關聯起來。ThreadLocal
提供get
和set
等介面或方法,這些方法為每一個使用這個變數的執行緒都存有一份獨立的副本,因此get
總是返回由當前執行緒在呼叫set
時設定的最新值。ThreadLocal
有如下方法
get()
方法是用來獲取ThreadLocal
在當前執行緒中儲存的變數副本
set()
用來設定當前執行緒中變數的副本
remove()
用來移除當前執行緒中變數的副本
initialValue()
是一個protected
方法,一般是用來在使用時進行重寫的,如果在沒有set的時候就呼叫get
,會呼叫initialValue
方法初始化內容。
為了使用的更放心,我們簡單的看一下具體的實現:
set
方法
set
方法會獲取當前的執行緒,通過當前執行緒獲取ThreadLocalMap
物件。然後把需要儲存的值放到這個map
裡面。如果沒有就呼叫createMap
建立物件。
getMap
方法
getMap
方法直接返回當前Thread
的threadLocals
變數,這樣說明了之所以說ThreadLocal
是執行緒區域性變數
就是因為它只是通過ThreadLocal
把變數
存在了Thread
本身而已。
createMap
方法
在set
的時候如果不存在threadLocals
,直接建立物件。由上看出,放入map
的key
是當前的ThreadLocal
,value
是需要存放的內容,所以我們設定屬性的時候需要注意存放和獲取的是一個ThreadLocal
。
get 方法
get
方法就比較簡單,獲取當前執行緒,嘗試獲取當前執行緒裡面的threadLocals
,如果沒有獲取到就呼叫setInitialValue
方法,setInitialValue
基本和set
是一樣的,就不累累述了。
場景
本文應用ThreadLocal
的場景:在呼叫API介面的時候傳遞了一些公共引數,這些公共引數攜帶了一些裝置資訊,服務端介面根據不同的資訊組裝不同的格式資料返回給客戶端。假定伺服器端需要通過裝置型別(device)來下發下載地址,當然介面也有同樣的其他邏輯,我們只要在返回資料的時候判斷好是什麼型別的客戶端就好了。如下:
場景一
請求
返回
場景二
請求
返回
實現
首先準備一個BaseSigntureRequest
類用來存放公共引數
然後準備一個static
的ThreadLocal
類用來存放ThreadLocal
,以便儲存和獲取時候的ThreadLocal
一致。
然後編寫一個Interceptor
,在請求的時候獲取device
引數,存入當前執行緒的ThreadLocal
中。這裡需要注意的是,重寫了afterCompletion
方法,當請求結束的時候把ThreadLocal
remove
,移除不必須要鍵值對。
當然需要在spring
裡面配置interceptor
最後在Converter
裡面轉換實體的時候直接使用即可,這樣就大功告成了。
總結
這種機制很方便,因為他避免了在呼叫每一個方法時都要傳遞執行上下文資訊,合理的使用ThreadLocal
可以起到事倍功半的效果,但是需要避免濫用,例如將所有的全域性變數作為ThreadLocal
物件,ThreadLocal
類似全域性變數,他能降低程式碼的可重用性,並在類之間引入隱含的耦合性,所以再使用前需要格外小心。
原文釋出時間為:2018-09-15
本文作者:麻醬