用ThreadLocal來優(yōu)化下代碼吧
最近接手了一個老項目,看到一個很有意思的現象。
這個項目中大量的方法入參都會帶上user信息,比如這樣
它的意圖是希望在方法內使用user的信息,但是如此大范圍的傳遞用戶信息,第一感覺就是不優(yōu)雅。那有什么辦法可以優(yōu)化一下呢?
我們第一反應是,可以存一個全局變量,在初始位置將用戶信息存入全局變量,然后在需要的地方去get一下。
那在WEB應用中,每個請求都是一個獨立線程,怎么去標記呢?
可以用線程的id去作為map的key,將該請求的用戶信息作為map的value。
沒錯,Java已經幫我們封裝好了這么一個對象,它就是我們今天要說的ThreadLocal。
- 什么是ThreadLocal
- 如何使用ThreadLocal優(yōu)化userid層層傳遞的問題
- ThreadLocal原理是啥
- ThreadLocal的實戰(zhàn)要點
1.什么是ThreadLocal
先來看下JDK的注釋:
簡單翻譯過來,就是說:
- ThreadLocal提供了線程隔離的局部變量,通過get( )和set( )方法操作當前線程對應的變量,而且不會和其他線程沖突,實現了基于線程的數據隔離。
2.如何使用ThreadLocal進行優(yōu)化
話不多說,基于我們開頭的例子,我迫不及待地用ThreadLocal來優(yōu)化一下。
2.1 構建基于ThreadLocal的上下文
定義一個SessionUser類,存儲用戶信息,包括用戶id、用戶名。
然后定義一個基于ThreadLocal的上下文SessionUserContext,代碼如下所示。
2.2 信息存入ThreadLocal中
在我們的優(yōu)化案例中,就是存入用戶信息。
解析請求中的用戶信息有很多方法。本文以HandlerIntercept為例,說明下MVC中的一種方式。
- 實現HandlerIntercept接口
- 重寫preHandler方法
- 解析HttpServletRequest,獲取用戶信息
- 用戶信息存于SessionUserContext
源碼如下所示。
2.3 在需要的地方獲取信息
原本需要傳入CurrentUser的參數都可以去掉了。
在需要用戶信息的時候,直接從SessionUserContext中獲取即可。
哈哈,是不是看起來一下子清爽了很多。
可以在任何地方獲取user信息,不再需要層層傳遞用戶信息了。
3.ThreadLocal實現原理
上面我們已經知道了怎么通過ThreadLocal進行優(yōu)化。
下面,我們要 知其然知其所以然,一起看看ThreadLocal實現原理吧。
3.1 set方法
Set方法應該是ThreadLocal的核心邏輯了。
主要三步:
獲取當前線程
- 獲取ThreadLocalMap對象
- 如果ThreadLocalMap對象存在,則將當前線程對象作為key,要存儲的對象作為value存到map中 如果ThreadLocalMap對象不存在,就調用creatMap( )進行創(chuàng)建
3.2 ThreadLocalMap是什么。
ThreadLocalMap是一個定義在ThreadLocal類內部的靜態(tài)類,里面還定義了一個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.實戰(zhàn)要點
在一開始的優(yōu)化設計中,不知道大家有沒有注意到對ThreadLocal的remove調用。
這里就需要談談ThreadLocal使用時的,兩個要點。尤其是在使用線程池的時候使用ThreadLocal。
4.1 避免內存泄露
在ThreadLocalMap介紹的時候,我們可以看到,ThreadLocalMap是Thread的一個屬性。因此,ThreadLocalMap和Thread的生命周期是一樣的。
如果沒有手動刪除對應的ThreadLocal的key,那么就會造成內存泄漏無法回收。尤其在線程池環(huán)境下,線程會被不斷復用。
4.2 線程池避免重復線程變量影響
以上文優(yōu)化案例為例。
在MVC中,每次請求進來會使用線程池復用線程。如果請求帶了用戶信息,那么就會重置ThreadLocal對應的用戶信息,如果請求沒有帶用戶信息,必須手動清除一下當前ThreadLocal對應的變量,否則后面使用過程中可能會造成混亂。